BetterFxP for Tampermonkey

מעולם לא היה קל יותר לגלוש ב-FxP.

当前为 2025-08-07 提交的版本,查看 最新版本

// ==UserScript==
// @name         BetterFxP for Tampermonkey
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  מעולם לא היה קל יותר לגלוש ב-FxP.
// @author       You
// @match        https://www.fxp.co.il/*
// @supportURL   https://discord.gg/AW7CeG7
// @require      https://update.greasyfork.org/scripts/439099/1203718/MonkeyConfig%20Modern%20Reloaded.js
// @icon         https://lh3.googleusercontent.com/j-CdJwaXX0eoqlMDLLYfbYTuuaFUM5Ep-Mph1UNktCZSYbm665WoIwGGw4d1iXxQWkLMDYior_xS8OKfWCBf1i4srw=s120
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @grant        GM_addElement
// @grant        GM_getResourceText
// @grant        GM_registerMenuCommand
// @grant        GM_addValueChangeListener
// @run-at       document-start
// @resource pms https://update.greasyfork.org/scripts/476628/1259426/fxp%20anti-delete%20PMs.user.js
// @license      MIT
// ==/UserScript==
// @noframes
const cfg = new MonkeyConfig({
    title: 'הגדרות FxPlus+',
    menuCommand: true,
    params: {
        hideBigImages: {
            label: "הסתר את הכתבות הגדולות מדך הבית",
            type: 'checkbox',
            default: false
        },
        hideGames: {
            label: "הסתר את אזור המשחקים מדף הבית",
            type: 'checkbox',
            default: false

        },
        hideArticles: {
            label: "הסתר כתבות מדף הבית",
            type: 'checkbox',
            default: false
        },
        resizeSignatures: {
            label: "חותך חתימות גדולות",
            type: 'checkbox',
            default: false
        },
        hideAds: {
            label: "הסתר מודעות",
            type: 'checkbox',
            default: false
        },
        hideNagish: {
            label: "הסתר את תפריט הנגישות",
            type: 'checkbox',
            default: false
        },
        showFriends: {
            label: "הצג חברים באשכולת",
            type: 'checkbox',
            default: false
        },
        showAutoPinned: {
            label: "הצג יותר משלושה אשכולות נעוצים",
            type: 'checkbox',
            default: false
        },
        disableLiveTyping: {
            label: "אל תודיע שאני מקליד",
            type: 'checkbox',
            default: false
        },
        showDeletedPost: {
            label: "הצג פוסט שנמחק",
            type: 'checkbox',
            default: false
        },
        showLikeLimit: {
            label: "הצג מגבלת לייקים",
            type: 'checkbox',
            default: false
        },
        connectedStaff: {
            label: "הצג צוות מחובר",
            type: 'checkbox',
            default: false
        },
        audioChange: { // https://www.tzevaadom.co.il/static/sounds/calm.wav
            label: ":קישור לקובץ שמע עבור התראה",
            type: 'text',
            default: ''
        },
        hideCategories: { // 4428, 13
            label: ":רשימה של קטגוריות להסתרה",
            type: 'text',
            default: ''
        },
        smiles: { // https://yoursmiles.org/tsmile/heart/t4524.gif
            label: ':רשימה של קישורים לסמיילים',
            type: 'text',
            default: '',
            long: 3
        },
        showCounts: {
            label: 'מציג את מספר הפוסטים ואת כמות המשתמשים המחוברים',
            type: 'checkbox',
            default: false,
        },
        pms: {
            label: "מציג הודעות פרטיות שנמחקו",
            type: "checkbox",
            default: false
        },
        showForumStats: {
            label: 'הצג סטטיסטיקות פורומים',
            type: "checkbox",
            default: false
        }
    }
});

document.addEventListener('keydown', function (e) {
    if (e.ctrlKey && e.key.toLowerCase() === 'y') {
        e.preventDefault();
        cfg.open("window", {
            windowFeatures: {
                width: 500, 
                height: 500
            }
        });
    }
});

/*
CKEDITOR.tools.callFunction(41, this); //131,'almoni-dl'
The system that automatically disables and enables the feature is currently not working in either version.
TODO:
- Implement audio file upload
- Support multiple users
- Like add http to check
- disable/enable on the same page and prevent reload
- Add features:
  - Auto night mode
  - Auto reader mode
  - BBCode support
  - Hide sticky posts
*/
const rawWindow = unsafeWindow;
const queryParams = new URLSearchParams(location.search);

function waitForObject(path) {
    return new Promise((resolve, reject) => {
        const timer = setInterval(() => {
            const obj = path.split('.').reduce((o, key) => (o && key in o ? o[key] : undefined), rawWindow);
            if (typeof obj !== "undefined") {
                clearInterval(timer);
                resolve(obj);
            }
        }, 100);
    });
}

function onMatchIfLoggedIn(match, permissions, callback) {
    rawWindow.LOGGEDIN && onMatch(match, permissions, callback);
}

// window.addEventListener('storage', (e) => {
// 	console.log('Storage changed!');
// 	console.log('Key:', e.key);
// 	console.log('Old Value:', e.oldValue);
// 	console.log('New Value:', e.newValue);
// 	console.log('Storage Area:', e.storageArea);
// 	console.log('URL:', e.url);
// });

function onMatch(match, permission, callback) {
    const hasPermission = permission === "none" || cfg.get(permission);
    if (!shouldRun(match) || !hasPermission) return;

    const docReady = /complete|interactive/.test(document.readyState);
    const runImmediately = !callback.toString().includes("document");

    let teardown = () => {};
    const executeFeature = () => {
        teardown();
        teardown = callback() || (() => {});
    };

    if (runImmediately || docReady) {
        executeFeature();
    } else {
        document.addEventListener('DOMContentLoaded', executeFeature);
    }

    GM_addValueChangeListener(permission, (key, oldVal, newVal) => {
        const func = newVal ? executeFeature : teardown;
        func();
    });
}

function injectStyle(match, permissions, css) {
    onMatch(match, permissions, function() {
        console.log(css);
        const styleElement = GM_addStyle(css);
        return () => styleElement?.remove();
    });
}

function shouldRun(matchPattern) {
    const urlPath = '/' + location.href.split('/').pop();
    const pattern = new RegExp(matchPattern.replace('*', '.*'));
    return pattern.test(urlPath);
}

function fetcher(url) {
    return fetch(url).then(response => response.text());
}
// Author ID: 967488
injectStyle("forumdisplay", "showAutoPinned", "#stickies li.threadbit:nth-child(n+4) { display: list-item !important; } .morestick { display: none !important; }");
injectStyle("*", "hideAds", "#adfxp, #related_main, .trc_related_container, .trc_spotlight_widget, .videoyoudiv, .OUTBRAIN { display: none !important }");
injectStyle("*", "hideNagish", ".nagish-button { display: none; }");
injectStyle("/(?:index.php)?", "hideCategories", `${cfg.get("hideCategories").split(", ").map(cId => `.hp_category:has(a[href="forumdisplay.php?f=${cId}"])`).join(',')} { display: none }`) // #cat${cId}, .hi4 { height: 337px }
injectStyle("/(?:index.php)?", "hideArticles", "#slide { height:auto !important; } .mainsik { display: none; }");
injectStyle("/(?:index.php)?", "hideBigImages", ".big-image-class { display: none; }");
injectStyle("/(?:index.php)?", "hideGames", "#slide ~ div h1, .fxp2021_Games { display: none !important;");
// חותך חתימות גדולות לגודל המותר וזה תלוי במשתמש במקום באתר לשנות את החתימה שתתאים
injectStyle("show(post|thread)|member.php", "resizeSignatures", ".signaturecontainer { max-width: 500px; max-height: 295px; overflow: hidden; }");

onMatchIfLoggedIn("private_chat.php?do=showpm|show(post|thread)", "disableLiveTyping", function() {
    const originalSendTypingInThread = rawWindow?.sendUserIsTypingInShowthread;
    const originalTypingSend = rawWindow?.typeingsend;

    rawWindow.sendUserIsTypingInShowthread = () => {};
    rawWindow.typeingsend = () => {};

    return () => {
        rawWindow.sendUserIsTypingInShowthread = originalSendTypingInThread;
        rawWindow.typeingsend = originalTypingSend;
    }
});
onMatchIfLoggedIn("signature", "none", function() {
    const publishedThreadUrl = "https://www.fxp.co.il/showthread.php?t=16859147";

    GM_addStyle(`
        #creditAddon { padding: .5em; text-align: center; }
        .addCreditBtn { margin: .2em 1em; background: #fff; border-radius: .2em; font-weight: 700; color: #004b67; border: 1px solid #c5c5c5; cursor: pointer; padding: 0; position: relative; width: 60px; height: 60px; display: inline-flex; justify-content: center; align-items: center; overflow: hidden; }
        .addCreditBtn img { border: 0; height: 100%; }
        .addCreditBtn .addCreditDesc { position: absolute; left: 0; top: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; color: #fff; background: rgba(0,0,0,.4); opacity: 1; }
        .addCreditBtn .addCreditDesc:hover { opacity: 0; }
        .addCreditBtn#addTextCredit .addCreditDesc:hover { opacity: 1; }
        .addCreditBtn#addXLimg { width: 110px; }
    `);

    const creditAddon = GM_addElement('div', {
        id: 'creditAddon',
    })
    creditAddon.innerHTML = `
    <div>שתף את הכיף!™ והוסף קרדיט לתוסף +FxPlus בחתימה שלך:</div>
    <div class="addCreditBtn" id="addLimg">
        <img src="https://i.imagesup.co/images2/b059514f80af8c5ec69afc73356a4dfa3b771343.png">
        <span class="addCreditDesc">128x128</span>
    </div>
    <div class="addCreditBtn" id="addMimg">
        <img src="https://i.imagesup.co/images2/7797f421f0e4895878e51d09266a35355b214d5a.png">
        <span class="addCreditDesc">48x48</span>
    </div>
    <div class="addCreditBtn" id="addTextCredit">
        <span class="addCreditDesc">טקסט</span>
    </div>`

    creditAddon.querySelectorAll('#addLimg, #addMimg, #addTextCredit').forEach(element => {
        element.addEventListener('click', (event) => {
            const imgElement = event.target.parentElement.querySelector('img');
            const iframeBody = document.querySelector(".cke_contents iframe").contentDocument.body;
            const creditLink = document.createElement('a');
            creditLink.href = publishedThreadUrl;
            creditLink.target = '_blank';

            if (imgElement) {
                const img = document.createElement('img');
                img.src = imgElement.src;
                creditLink.appendChild(img);
            } else {
                creditLink.textContent = '+FxPlus';
            }
            iframeBody.appendChild(creditLink);
        });
    });
    const smilieBox = document.querySelector('form[action*="signature"] .editor_smiliebox');
    smilieBox.parentNode.insertBefore(creditAddon, smilieBox);
});
onMatchIfLoggedIn("show(post|thread)", "showFriends", function() {
    (async () => {
        const REFRESH_INTERVAL = 15 * 60 * 1000; // 15 minutes in milliseconds
        const storageKey = "refreshFriends";
        const storedTime = GM_getValue(storageKey);
        if (storedTime) {
            const timeSinceLastRefresh = new Date().getTime() - new Date(storedTime).getTime();
            if (timeSinceLastRefresh < REFRESH_INTERVAL) {
                return console.log('Friends list is still fresh, skipping refresh.');
            }
        }
        let allFriendIds = [],
            temp = []
        let page = 1,
            match;
        const regex = /<h4><a href="member\.php\?u=(\d+)"/g;
        while (temp.size < 100) {
            temp = [];
            const url = `https://www.fxp.co.il/profile.php?do=buddylist&pp=100&page=${page}`;
            const html = await fetcher(url);

            while ((match = regex.exec(html)) !== null) {
                temp.push(match[1]);
            }
            allFriendIds = allFriendIds.concat(temp);
            console.log(`Page ${page}: Total friend IDs collected so far: ${allFriendIds.size}`);

            // If the response seems to be exhausted or doesn't bring in new friends, break.
            if (!match) break;

            page++;
        }

        console.log('Fetched friend IDs:', allFriendIds);

        GM_setValue('friendIds', JSON.stringify(allFriendIds));
        GM_setValue(storageKey, Date.now());
    })();

    const friendIds = JSON.parse(GM_getValue("friendIds", '[]'));
    if (!friendIds) return;
    let css = friendIds.map(id => '.username[href$="' + id + '"]::after').join(', ')
    let styleElement = GM_addStyle(`
        ${css} {
            content: "";
            display: inline-block;
            width: 20px;
            height: 20px;
            background-image: url('https://w7.pngwing.com/pngs/236/25/png-transparent-computer-icons-avatar-friends-love-text-logo-thumbnail.png');
            background-size: cover;
    }`)
    return () => styleElement?.remove()
})
onMatchIfLoggedIn("*", "audioChange", function() {
    let isFeatureEnabled = true;
    Object.defineProperty(HTMLAudioElement.prototype, 'src', {
        set: function(value) {
            if (isFeatureEnabled && value === "https://images4.fxp.co.il/nodejs/sound.mp3") {
                value = cfg.get("audioChange");
            }
            this.setAttribute('src', value);
        },
        get: () => this.getAttribute('src'),
        configurable: true,
        enumerable: true,
    });
    return () => {
        isFeatureEnabled = false;
    }
})
onMatchIfLoggedIn("show(post|thread)", "smiles", async function() {
    const images = cfg.get("smiles").trim().split('\n');
    if (images.length < 1) return;

    const editor = await waitForObject("vB_Editor.vB_Editor_QR");
    console.log("vB_Editor is available:", editor);
    let temp = editor.config.smiley_descriptions,
        tmp = editor.config.smiley_images;
    for (const image of images) {
        if (!image) continue;
        editor.config.smiley_descriptions.push(`[img]${image}[/img]`);
        editor.config.smiley_images.push(`https://wsrv.nl/?url=${image}&w=30`);
    }

    return () => {
        editor.config.smiley_descriptions = temp;
        editor.config.smiley_images = tmp;
    }
})
onMatchIfLoggedIn("show(post|thread)", "showLikeLimit", function() {
    const db = 'likes' + (window.USER_ID_FXP || '0');
    const likeLimit = 15;
    const selector = '.button-like-holder > span[onclick="makelike(this.id);"]';

    let likes = JSON.parse(GM_getValue(db, '[]'));
    let styleElement;
    const updateLikeButton = () => {
        console.log(likes);
        if (likes.length >= likeLimit) {
            styleElement = GM_addStyle(`${selector} { background-image: url("https://em-content.zobj.net/source/google/387/broken-heart_1f494.png"); }`);
        } else {
            // styleElement?.remove()
        }
    };

    const updateTimer = () => {
        const oldestLike = Math.min(...likes);
        if (Date.now() > oldestLike) {
            likes = likes.filter(like => like !== oldestLike);
            GM_setValue(db, JSON.stringify(likes));
            if (likes.length === 0) {
                clearInterval(timer);
            }
        }
    };

    const timer = setInterval(updateTimer, likeLimit * 3000);

    updateLikeButton();
    updateTimer();

    document.querySelectorAll(selector).forEach(element => {
        element.addEventListener('click', function() {
            if (likes.length >= likeLimit) return;
            likes.push(Date.now() + likeLimit * 60 * 1000);
            GM_setValue(db, JSON.stringify(likes));
            updateLikeButton();
        })
    })

    return () => styleElement?.remove();
})
// https://greasyfork.org/en/scripts/476628-fxp-anti-delete-pms
onMatchIfLoggedIn("do=showpm&pmid=", "pms", async function() {
    await waitForObject("socket");
    new Function(GM_getResourceText("pms")).apply(rawWindow);
})
onMatch("show(post|thread)", "showDeletedPost", function() {
    const targetPostId = queryParams.get('p');
    const isPostExist = document.contains(document.getElementById('post_' + targetPostId));
    if (!targetPostId || isPostExist) {
        return;
    }
    const elements = Array.from(document.querySelectorAll('.postbit'));
    const postIds = elements.map(el => parseInt(el.id.replace('post_', '')));
    const index = postIds.filter(pid => pid < targetPostId).length - 1;
    // if (index === 0) index = 1; //need to be test
    const newElement = GM_addElement("li", {
        textContent: 'התגובה שאתה מנסה לראות נמחקה.',
        id: 'post_' + targetPostId,
        className: 'postbit postbitim postcontainer', //test how this show (innerHTML)
        style: "background-color: #ffdddd; border: 1px solid #ff0000; padding: 10px 0; border-radius: 5px; color: #333; font-weight: bold; text-align: center;"
    })
    const targetElement = elements.at(index);
    targetElement.parentNode.insertBefore(newElement, targetElement.nextSibling);

    if (!queryParams.has('t')) {
        setTimeout(() => targetElement.scrollIntoView(), 500);
    }

    return () => newElement?.remove();
});
onMatch("forumdisplay", "connectedStaff", function() {
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('width', '10px');
    svg.setAttribute('height', '10px');
    svg.setAttribute('viewBox', '0 0 24 24');

    const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('fill', '#00FF00');
    path.setAttribute('d', 'm2 12a10 10 0 1 1 10 10 10 10 0 0 1 -10-10z');

    svg.appendChild(path);

    const usernameElements = document.querySelectorAll('.flo .username');

    usernameElements.forEach(async usernameElement => {
        const username = usernameElement.innerText;
        const userLink = usernameElement.href;

        const html = await fetcher(userLink);
        if (html.includes(username + ' מחובר/ת')) {
            usernameElement.insertAdjacentHTML('beforeend', svg.outerHTML);
        }
    });

    return () => {
        usernameElements.forEach(({
            lastChild
        }) => lastChild.tagName === 'svg' && lastChild.remove());
    }
});
onMatch("*", "showCounts", function() {
    const scripts = document.querySelectorAll('script[type="text/javascript"]:not([src])');
    const script = Array.from(scripts).find(e => e.innerText.includes("counts"));
    if (!script) return;

    const lines = script.innerText.split("\n");
    const counts = {};
    for (const line of lines) {
        const match = line.match(/counts\["(.+?)"\]\s*=\s*(\d+);/);
        if (match) counts[match[1]] = parseInt(match[2]);
    }

    const container = document.createElement("div");
    container.style.position = "fixed";
    container.style.bottom = "10px";
    container.style.right = "10px";
    container.style.background = "white";
    container.style.border = "1px solid #ccc";
    container.style.padding = "10px 15px";
    container.style.boxShadow = "0 0 5px rgba(0,0,0,0.2)";
    container.style.borderRadius = "8px";
    container.style.zIndex = "9999";
    container.style.fontFamily = "Arial, sans-serif";
    container.style.fontSize = "14px";
    container.style.transition = "opacity 1s";

    container.innerHTML = `
        <strong>סטטיסטיקה:</strong><br>
        מחוברים: ${counts["#total_online"]?.toLocaleString() ?? 'N/A'}<br>
        פוסטים: ${counts["#total_posts"]?.toLocaleString() ?? 'N/A'}
    `;

    document.body.appendChild(container);
    setTimeout(() => {
        container.style.opacity = "0";
        container.addEventListener("transitionend", container.remove);
    }, 5000);
});
/*
the old messy code with all those functions
maybe one day I'll have AI rewrite it in cleaner vanilla JavaScript with fewer indentations.
*/
onMatch("forumdisplay", "showForumStats", function() {
    GM_addStyle(`
        #forumStatsContainer {
    width: 100%;
    clear: both;
    background: #feffe5;
    border: 1px solid #C4C4C4;
    border-top: none;
    border-bottom-left-radius: 5px;
    border-bottom-right-radius: 5px;
    box-sizing: border-box;
}

#forumStats {
    padding: 1em;
    text-align: center;
    position: relative;
    padding-bottom: 1.5em;
}

.statTable {
    border: 1px solid #f1f1f1;
    margin: 0 0.5em;
    margin-bottom: 1em;
    border-top: 0;
    border-bottom: 0;
}

.statTable th {
    font-weight: bold;
    text-align: center;
    padding: 0.5em;
}

.statTable td {
    padding: 0.1em 0.5em;
}

#detailedStatsBtn {
    position: absolute;
    bottom: 0;
    left: 50%;
    border-bottom: none !important;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
    transform: translateX(-50%);
}
    
.dimScreen {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.75);
    z-index: 111;
    display: none;
}

#popupBox {
    z-index: 1200;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

.popupContainer {
    pointer-events: all;
    animation: fadeInTop 0.3s;
    display: none;
}

    .popupContainer.greenTopPopup .popupTop {
        background: #1d9655;
    }

    .popupContainer.orangeTopPopup .popupTop {
        background: #ea861c;
    }

    .popupContainer.alertTopPopup .popupTop {
        background: #ef1700;
    }

.popupTop {
    background: #1CA4EA;
    color: #fff;
    font-size: 1.4em;
    border-top-left-radius: 0.2em;
    border-top-right-radius: 0.2em;
    padding: 0.5em 2em;
    text-align: center;
}

    .popupTop .subtitle {
        font-size: 0.5em;
        color: rgba(255, 255, 255, 0.64);
    }

.popupImg {
    width: 3em;
    height: 3em;
    background: inherit;
    padding: 0.2em;
    border-radius: 50%;
    margin: 0 auto;
    margin-top: -1.8em;
}

.popupBottom {
    background: #fff;
    border-bottom-left-radius: 0.2em;
    border-bottom-right-radius: 0.2em;
    padding: 1em 2em;
    text-align: center;
    max-height: calc(80vh - 3em);
    max-width: 90vw;
    box-sizing: border-box;
    overflow: auto;
}
`)

    function getDupeSortedDictionary(arr) {
        var counts = {}; //count each one
        for (var i = 0; i < arr.length; i++) {
            counts[arr[i]] = (counts[arr[i]] || 0) + 1;
        }

        var sortedArr = []; //add all properties to an array
        for (var prop in counts) {
            sortedArr.push({
                value: prop,
                count: counts[prop]
            });
        }
        sortedArr.sort(function(a, b) //sort the array according to the counts or alphabetically
            {
                if (b.count === a.count) {
                    if (b.value < a.value)
                        return 1;
                    else if (b.value > a.value)
                        return -1;
                    else
                        return 0;
                } else
                    return b.count - a.count;
            });
        return sortedArr;
    }


    //adds a window with a specific id and dims the background
    function openPopupWindow(id, img, title, content, additionalClass) {
        if ($("#popupBox").length === 0) {
            $("body").append($("<div>", {
                id: "popupBox"
            }));
        }

        //do not open a popup if one already exists
        if ($(".popupContainer").length === 0) {
            $("body").append($("<div>", {
                class: "dimScreen",
                id: "dim_" + id
            }).click(function() {
                removePopupWindow(id);
            }));

            var popup =
                $("<div>", {
                    class: "popupContainer",
                    id: id
                }).append(
                    $("<div>", {
                        class: "popupTop"
                    }).append(
                        $("<div>", {
                            class: "popupImg"
                        }).append(
                            $("<img>", {
                                src: img
                            })
                        )
                    ).append(
                        $("<div>", {
                            class: "popupTitle"
                        }).append(title)
                    )
                ).append(
                    $("<div>", {
                        class: "popupBottom"
                    }).append(content)
                );

            if (additionalClass)
                popup.addClass(additionalClass);

            $("#popupBox").append(popup);

            $("#dim_" + id).fadeIn(300, function() {
                $("#" + id).show();
            });
        }
    }

    //removes completely a popup window
    function removePopupWindow(id) {
        if ($("#" + id).length > 0) {
            $("#" + id).remove();
            $("#dim_" + id).fadeOut(300, function() {
                $(this).remove();
            });
            return true;
        }
        return false;
    }
    const total = document.querySelectorAll('#threads .threadtitle').length;
    $(".threads_list_fxp").after(
        $("<div>", {
            id: "forumStatsContainer"
        }).append(
            $("<div>", {
                id: "forumStats"
            }).append(
                $("<i>").text(`נתונים סטטיסטיים של ${total} אשכולות:`)
            )
        )
    );

    //PUBLISHERS

    var publishers = []; //array of all posters that posted in the forum
    $("#threads .threadinfo .username").each(function() {
        publishers.push($(this).text());
    });
    var publishersDict = getDupeSortedDictionary(publishers);
    console.log(publishers, publishersDict);

    line = $("<div>");
    if (publishersDict.length > 1 && publishersDict[0].count > 1) {
        line.append($("<span>").text("המפרסם הדומיננטי ביותר הוא "));
        //add names until the count is not the largest
        for (var i = 0; i < publishersDict.length && (publishersDict[i].count === publishersDict[0].count); i++) {
            if (i > 0)
                line.append($("<span>").text(" או "));
            line.append($("<b>").text(publishersDict[i].value));
        }
        line.append($("<span>").text(" עם " + publishersDict[0].count + " אשכולות."));
    } else {
        line.append($("<span>").text("אין מפרסם דומיננטי במיוחד."));
    }
    $("#forumStats").append(line);

    //COMMENTORS

    var commentors = []; //array of all commentors that last posted in threads
    $("#threads .threadlastpost .username").each(function() {
        commentors.push($(this).text());
    });

    var commentorsDict = getDupeSortedDictionary(commentors);

    line = $("<div>");
    if (commentorsDict.length > 1 && commentorsDict[0].count > 1) {
        line.append($("<span>").text("המגיב האחרון הדומיננטי ביותר הוא "));
        for (var i = 0; i < commentorsDict.length && (commentorsDict[i].count === commentorsDict[0].count); i++) {
            if (i > 0)
                line.append($("<span>").text(" או "));
            line.append($("<b>").text(commentorsDict[i].value));
        }
        line.append($("<span>").text(" עם " + commentorsDict[0].count + " תגובות אחרונות."));
    } else {
        line.append($("<span>").text("אין מגיב אחרון דומיננטי במיוחד."));
    }
    $("#forumStats").append(line);

    //WORDS

    var words = []; //array of all words in titles
    $("#threads .title").each(function() {
        var titleWords = $(this).text().match(/([^\s.,\/#?!$%\^&\*+;:{}|=\-_`~()]+)/g); //get words in title
        if (titleWords !== null) {
            titleWords.forEach(function(word) {
                if (word.length > 1) //push words to the array of all the words
                    words.push(word);
            });
        }
    });

    var wordsDict = getDupeSortedDictionary(words);

    var line = $("<div>");
    if (wordsDict.length > 1 && wordsDict[0].count > 1) {
        line.append($("<span>").text("המילה הנפוצה ביותר בכותרות היא "));
        for (var i = 0; i < wordsDict.length && (wordsDict[i].count === wordsDict[0].count); i++) {
            if (i > 0)
                line.append($("<span>").text(" או "));
            line.append($("<b>").text(wordsDict[i].value));
        }
        line.append($("<span>").text(" עם " + wordsDict[0].count + " אזכורים."));
    } else {
        line.append($("<span>").text("אין מילה נפוצה במיוחד בכותרות."));
    }
    $("#forumStats").append(line);

    //PREFIXES

    var prefixes = []; //array of all prefixes of threads
    $("#threads .prefix").each(function() {
        var prefix = $(this).text().trim();
        prefix = prefix.replace("|", ""); //remove |
        prefix = prefix.replace("סקר: ", "").trim(); //remove poll prefix
        prefixes.push(prefix);
    });

    var prefixesDict = getDupeSortedDictionary(prefixes);

    line = $("<div>");
    if (prefixesDict.length > 1 && prefixesDict[0].count > 1) {
        line.append($("<span>").text("התיוג הנפוץ ביותר הוא "));
        for (var i = 0; i < prefixesDict.length && (prefixesDict[i].count === prefixesDict[0].count); i++) {
            if (i > 0)
                line.append($("<span>").text(" או "));
            line.append($("<b>").text(prefixesDict[i].value));
        }
        line.append($("<span>").text(" שנמצא ב-" + prefixesDict[0].count + " אשכולות."));
    } else {
        line.append($("<span>").text("אין תיוג נפוץ במיוחד."));
    }
    $("#forumStats").append(line);

    //VIEW COMMENT RATIO

    var commentsCount = 0;
    var viewsCount = 0;

    var cc, vc;

    $("#threads .threadstats").each(function() {
        cc = parseInt($(this).find("li:eq(0)").text().replace(",", "").replace(/^\D+/g, ""));
        vc = parseInt($(this).find("li:eq(1)").text().replace(",", "").replace(/^\D+/g, ""));
        if (!isNaN(cc))
            commentsCount += cc;
        if (!isNaN(vc))
            viewsCount += vc;
    });

    var viewsCommentsRatio = Math.round(viewsCount / commentsCount);
    if (viewsCommentsRatio < 1)
        viewsCommentsRatio = 1; //make sure that it's not rounded to 0

    if (isNaN(viewsCommentsRatio))
        viewsCommentsRatio = "∞";

    line = $("<div>");
    line.append($("<span>").text("יחס הצפיות לתגובה הוא תגובה כל "));
    line.append($("<b>").text(viewsCommentsRatio + " צפיות"));
    line.append($("<span>").text("."));
    $("#forumStats").append(line);


    //shorten the words dictionary
    var shortWordsDict = [];
    for (var i = 0; i < wordsDict.length && wordsDict[i].count > 1; i++) {
        shortWordsDict.push(wordsDict[i]);
    }
    wordsDict = shortWordsDict;

    //button for detailed statistics
    $("#forumStats").append(
        $("<div>", {
            class: "smallPlusButton",
            id: "detailedStatsBtn"
        }).text("+").click(function() {

            var pContent = $("<div>");

            pContent.append($("<div>").text("להלן פירוט הסטטיסטיקות לפורום זה:"));

            var flexTableContainer = $("<div>", {
                style: "display: flex; flex-wrap: wrap;"
            });

            //add table skeleton
            flexTableContainer.append($("<table>", {
                class: "statTable",
                id: "publishersStatTable"
            }).append(
                $("<tr>").append(
                    $("<th>").text("מפרסם")
                ).append(
                    $("<th>").text("אשכולות")
                )));

            flexTableContainer.append($("<table>", {
                class: "statTable",
                id: "commentorsStatTable"
            }).append(
                $("<tr>").append(
                    $("<th>").text("מגיב")
                ).append(
                    $("<th>").text("תגובות אחרונות")
                )));

            flexTableContainer.append($("<table>", {
                class: "statTable",
                id: "wordsStatTable"
            }).append(
                $("<tr>").append(
                    $("<th>").text("מילה")
                ).append(
                    $("<th>").text("אזכורים")
                )));

            flexTableContainer.append($("<table>", {
                class: "statTable",
                id: "prefixesStatTable"
            }).append(
                $("<tr>").append(
                    $("<th>").text("תיוג")
                ).append(
                    $("<th>").text("אשכולות")
                )));

            //add table content
            for (var i = 0; i < publishersDict.length; i++) {
                flexTableContainer.find("#publishersStatTable").append(
                    $("<tr>").append(
                        $("<td>").text(publishersDict[i].value)
                    ).append(
                        $("<td>").text(publishersDict[i].count)
                    )
                );
            }

            for (var i = 0; i < commentorsDict.length; i++) {
                flexTableContainer.find("#commentorsStatTable").append(
                    $("<tr>").append(
                        $("<td>").text(commentorsDict[i].value)
                    ).append(
                        $("<td>").text(commentorsDict[i].count)
                    )
                );
            }

            for (var i = 0; i < wordsDict.length; i++) {
                flexTableContainer.find("#wordsStatTable").append(
                    $("<tr>").append(
                        $("<td>").text(wordsDict[i].value)
                    ).append(
                        $("<td>").text(wordsDict[i].count)
                    )
                );
            }

            for (var i = 0; i < prefixesDict.length; i++) {
                flexTableContainer.find("#prefixesStatTable").append(
                    $("<tr>").append(
                        $("<td>").text(prefixesDict[i].value)
                    ).append(
                        $("<td>").text(prefixesDict[i].count)
                    )
                );
            }

            pContent.append(flexTableContainer);
            pContent.append(
                $("<div>", {
                    class: "closeBtn"
                }).text("סגור").click(function() {
                    removePopupWindow("detailedStats");
                })
            );
            openPopupWindow("detailedStats",
                "https://raw.githubusercontent.com/SilverTuxedo/FxPlusplus/refs/heads/master/chrome/images/graph.svg",
                "סטטיסטיקות מפורטות לפורום " + document.querySelector("[property=\"og:title\"]").content.replace("קהילת", "").trim(),
                pContent);
        }));
    return () => document.querySelectorAll("#forumStatsContainer, #popupBox").forEach(e => e.remove())
})
/*
onMatchReady("*", "nightMode", function(hideBigImages, hideGames) {
    let styleElement;
    function toggleDarkMode(isEnabled) {
        const newValue = isEnabled ? "1" : "0";
        const date = new Date();
        date.setTime(new Date().getTime() + 172800000);
        document.cookie = `bb_darkmode=${newValue}; expires=${date.toUTCString()}`
    }
    function timeInMinutes(timeString) {
        if (!timeString) return 0;
        const [hours, minutes] = timeString.split(':').map(Number);
        return hours * 60 + minutes;
    }

    function isNightModeEnabled() {
        const cookies = document.cookie.split(';');
        return cookies.some(cookie => cookie.trim().startsWith('bb_darkmode=1'));
    }

    const now = new Date();
    const minutesCurrent = now.getHours() * 60 + now.getMinutes();

    let rangeActive = false;
    const minutesStart = timeInMinutes('17:00'); //settings.autoNightmode.start
    const minutesEnd = timeInMinutes('23:50');
    //TODO ADD setTimeout

    rangeActive = minutesEnd < minutesStart ?
        (minutesStart <= minutesCurrent || minutesCurrent < minutesEnd) :
    (minutesCurrent >= minutesStart && minutesCurrent < minutesEnd);

    if (!isNightModeEnabled() && !rangeActive) {
        toggleDarkMode(false);
    } else if (rangeActive) {
        toggleDarkMode(true);
    }
    return () => {};
})
// const nightModeEnabled = localStorage.getItem("nightmodeEnabled") === "true";
// const temp = nightModeEnabled && settings.customBg.night.length > 0 ?
//         name: 'הפעל את מצב הלילה אוטומטית משעה <input type="time" id="nightStartTime" value="17:05" /> עד שעה <input type="time" id="nightEndTime" value="23:30" />',
//         default: {
// 			active: false,
// 			start: "17:05",
// 			end: "23:30"
// 		}
*/