Bitcoin Forum
August 29, 2026, 09:36:51 PM *
News: Latest Bitcoin Core release: 31.1 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: [Script] Bitcointalk Post Health  (Read 282 times)
Z_MBFM (OP)
Hero Member
*****
Online Online

Activity: 1218
Merit: 503



View Profile WWW
August 27, 2026, 05:55:20 PM
Last edit: August 28, 2026, 08:16:12 AM by Z_MBFM
Merited by Mia Chloe (2), masulum (1)
 #1

Bitcointalk Post Health Script
Note - To do this, I took help from nutildah postflayer.onrender.com and masulum extension https://addons.mozilla.org/en-US/firefox/addon/nutildah-postflyer/ and also help of AI

The purpose of creating this script
We all use various types of extension scripts through tampermonkey and Violentmonkey . Because they can be used very easily in extension supported browsers from desktop laptops to smartphones and multiple scripts can be used together. That is why I made this script so that all users can use it easily. To improve post quality, it is important to monitor your post health score and the post health score of others, it will be easy to analyze the posts of those who are posting well and improve your own posts. So I think this script should be installed by everyone.

To use this script, you need to download Chrome version tampermonkey or Violentmonkey and if you are using Mozilla Firefox, install the Firefox version tampermonkey or Violentmonkey

The browsers you can use for Android Device are: Mises browser, Mozilla Firefox, Lemur Browser, Kiwi Browser
You can directly add to tamperkmoney or Violentmonkey by one click Install this scrip- Bitcointalk Post Health

and also Create a new script on tamperkmoney or Violentmonkey (Copy the code bellow and paste there and save the script)
Note - Developer mode must be turned on for mobile browsers.

Code:
// ==UserScript==
// @name         Bitcointalk Post Health
// @namespace    https://postflayer.onrender.com/
// @version      3.0.0
// @description  Shows the Bitcointalk Post Health Score beside each poster in a topic.
// @author       Z_MBFM
// @license      MIT
// @homepageURL  https://postflayer.onrender.com/
// @supportURL   https://postflayer.onrender.com/
// @match        *://bitcointalk.org/index.php*
// @match        *://www.bitcointalk.org/index.php*
// @connect      postflayer.onrender.com
// @grant        GM_xmlhttpRequest
// @grant        GM.xmlHttpRequest
// @grant        GM_getValue
// @grant        GM.getValue
// @grant        GM_setValue
// @grant        GM.setValue
// @run-at       document-idle
// @noframes
// ==/UserScript==

/*
 * Compatibility notes
 * -------------------
 * Tampermonkey  : exposes both GM_* (sync) and GM.* (promise) APIs.
 * Violentmonkey : exposes both GM_* (sync) and GM.* (promise) APIs.
 * Greasemonkey 4: exposes ONLY the promise-based GM.* API. GM_xmlhttpRequest,
 *                 GM_getValue and GM_setValue do not exist there, which is why
 *                 every privileged call below goes through an adapter.
 *
 * The API at postflayer.onrender.com sends no Access-Control-Allow-Origin
 * header, so a plain window.fetch() from the page context is blocked by CORS.
 * GM_xmlhttpRequest / GM.xmlHttpRequest are not subject to that restriction,
 * and are therefore required rather than merely convenient.
 */

(() => {
    "use strict";

    // ---------------------------------------------------------------- config

    const API_ROOT = "https://postflayer.onrender.com";
    const BADGE_CLASS = "bth-health-grade";
    const STYLE_ID = "bth-style";
    const STORAGE_KEY = "bth-cache-v1";

    // Scores move slowly, so a positive result is worth keeping for a while.
    // "No score" is cached for less time in case the account starts qualifying.
    const TTL_OK_MS = 6 * 60 * 60 * 1000;
    const TTL_EMPTY_MS = 45 * 60 * 1000;

    // The API is hosted on a free Render instance: cold starts routinely take
    // 30+ seconds, so the timeout is deliberately generous and the number of
    // simultaneous requests deliberately small.
    const REQUEST_TIMEOUT_MS = 45000;
    const MAX_CONCURRENCY = 3;
    const MAX_RETRIES = 1;
    const RETRY_DELAY_MS = 1500;

    // Bounds the persisted cache so it cannot grow without limit.
    const CACHE_MAX_ENTRIES = 500;
    const CACHE_PRUNE_TO = 400;
    const CACHE_FLUSH_DEBOUNCE_MS = 1500;

    const DEBUG = false;

    function log(...args) {
        if (DEBUG) console.log("[BTH]", ...args);
    }

    function warn(...args) {
        console.warn("[BTH]", ...args);
    }

    const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

    // ------------------------------------------------- GM API: HTTP adapter

    /**
     * Resolves the best available cross-origin request function.
     * Returns null when the script is running without the required grant,
     * which lets the caller surface an accurate error instead of a CORS
     * failure that looks like the API being down.
     */
    function resolveRequester() {
        if (typeof GM_xmlhttpRequest === "function") {
            return { fn: GM_xmlhttpRequest, name: "GM_xmlhttpRequest" };
        }

        if (typeof GM !== "undefined" && GM && typeof GM.xmlHttpRequest === "function") {
            // Greasemonkey 4+ path. Bound to GM so `this` stays correct.
            return { fn: GM.xmlHttpRequest.bind(GM), name: "GM.xmlHttpRequest" };
        }

        return null;
    }

    const requester = resolveRequester();

    /**
     * Performs a GET and resolves with { status, responseText }.
     *
     * Handles the three shapes a userscript manager can present:
     *  - callback only (Tampermonkey / Violentmonkey GM_xmlhttpRequest)
     *  - promise only  (Greasemonkey 4 GM.xmlHttpRequest)
     *  - both at once  (Tampermonkey GM.xmlHttpRequest)
     * A `settled` guard makes the double-signalling case harmless.
     */
    function httpGet(url) {
        if (!requester) {
            return Promise.reject(new Error(
                "No cross-origin request API available. The script needs " +
                "@grant GM_xmlhttpRequest (or GM.xmlHttpRequest)."
            ));
        }

        return new Promise((resolve, reject) => {
            let settled = false;
            let handle = null;
            let timer = null;

            const finish = fn => value => {
                if (settled) return;
                settled = true;
                if (timer !== null) clearTimeout(timer);
                fn(value);
            };

            const succeed = finish(resolve);
            const fail = finish(reject);

            const onResponse = response => {
                if (!response) {
                    fail(new Error("Empty response"));
                    return;
                }
                succeed({
                    status: response.status,
                    responseText: response.responseText || ""
                });
            };

            // Greasemonkey does not reliably honour the `timeout` option, so an
            // independent guard is used for every manager.
            timer = setTimeout(() => {
                if (settled) return;
                try {
                    if (handle && typeof handle.abort === "function") handle.abort();
                } catch (e) {
                    /* aborting is best-effort */
                }
                fail(new Error("Request timed out after " + REQUEST_TIMEOUT_MS + "ms"));
            }, REQUEST_TIMEOUT_MS);

            const details = {
                method: "GET",
                url,
                timeout: REQUEST_TIMEOUT_MS,
                headers: { Accept: "application/json" },
                onload: onResponse,
                onerror: () => fail(new Error("Network error")),
                ontimeout: () => fail(new Error("Request timed out")),
                onabort: () => fail(new Error("Request aborted"))
            };

            try {
                handle = requester.fn(details);
            } catch (e) {
                fail(new Error("Request failed to start: " + (e && e.message || e)));
                return;
            }

            // Greasemonkey 4 returns a promise and ignores the callbacks above.
            if (handle && typeof handle.then === "function") {
                handle.then(onResponse, e => fail(
                    new Error("Network error: " + (e && e.message || e))
                ));
            }
        });
    }

    // ---------------------------------------------- GM API: storage adapter

    /**
     * Promise-based value storage that degrades gracefully:
     * GM_* (sync) -> GM.* (promise) -> localStorage -> in-memory only.
     * Greasemonkey 4 only has the promise form, so both are supported.
     */
    const store = (() => {
        const hasSync = typeof GM_getValue === "function" && typeof GM_setValue === "function";
        const hasAsync = typeof GM !== "undefined" && GM &&
            typeof GM.getValue === "function" && typeof GM.setValue === "function";

        if (hasSync) {
            return {
                name: "GM_setValue",
                get: key => Promise.resolve().then(() => GM_getValue(key, null)),
                set: (key, value) => Promise.resolve().then(() => GM_setValue(key, value))
            };
        }

        if (hasAsync) {
            return {
                name: "GM.setValue",
                get: key => Promise.resolve(GM.getValue(key, null)),
                set: (key, value) => Promise.resolve(GM.setValue(key, value))
            };
        }

        try {
            const probe = "__bth_probe__";
            localStorage.setItem(probe, "1");
            localStorage.removeItem(probe);

            return {
                name: "localStorage",
                get: key => Promise.resolve(localStorage.getItem(key)),
                set: (key, value) => Promise.resolve().then(() => localStorage.setItem(key, value))
            };
        } catch (e) {
            const mem = new Map();
            return {
                name: "memory",
                get: key => Promise.resolve(mem.has(key) ? mem.get(key) : null),
                set: (key, value) => Promise.resolve(mem.set(key, value))
            };
        }
    })();

    // ----------------------------------------------------------------- cache

    /**
     * uid -> { score: string|null, time: number }
     *
     * Loaded once on start, mutated in memory, and flushed back on a debounce
     * so a page with many posters performs a single write instead of one per
     * poster. Entries are pruned oldest-first to keep the stored blob small.
     */
    const cache = new Map();
    let cacheReady = null;
    let flushTimer = null;

    function loadCache() {
        if (cacheReady) return cacheReady;

        cacheReady = store.get(STORAGE_KEY).then(raw => {
            if (!raw) return;

            // GM_setValue can round-trip objects directly; localStorage cannot.
            const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
            if (!parsed || typeof parsed !== "object") return;

            const now = Date.now();

            for (const [uid, entry] of Object.entries(parsed)) {
                if (!entry || typeof entry.time !== "number") continue;
                if (isExpired(entry, now)) continue;

                cache.set(String(uid), {
                    score: typeof entry.score === "string" ? entry.score : null,
                    time: entry.time
                });
            }

            log("cache loaded:", cache.size, "entries via", store.name);
        }).catch(e => {
            warn("cache load failed, continuing without it:", e);
        });

        return cacheReady;
    }

    function isExpired(entry, now = Date.now()) {
        const ttl = entry.score ? TTL_OK_MS : TTL_EMPTY_MS;
        return now - entry.time >= ttl;
    }

    function pruneCache() {
        if (cache.size <= CACHE_MAX_ENTRIES) return;

        const byAge = [...cache.entries()].sort((a, b) => a[1].time - b[1].time);

        for (const [uid] of byAge.slice(0, cache.size - CACHE_PRUNE_TO)) {
            cache.delete(uid);
        }
    }

    function scheduleFlush() {
        if (flushTimer !== null) clearTimeout(flushTimer);

        flushTimer = setTimeout(() => {
            flushTimer = null;
            pruneCache();

            const plain = {};
            for (const [uid, entry] of cache) plain[uid] = entry;

            store.set(STORAGE_KEY, JSON.stringify(plain))
                .catch(e => warn("cache save failed:", e));
        }, CACHE_FLUSH_DEBOUNCE_MS);
    }

    function cacheGet(uid) {
        const entry = cache.get(uid);

        if (!entry) return null;

        if (isExpired(entry)) {
            cache.delete(uid);
            return null;
        }

        return entry;
    }

    function cachePut(uid, score) {
        cache.set(uid, { score, time: Date.now() });
        scheduleFlush();
    }

    // ------------------------------------------------- request queue / fetch

    /**
     * Bounded-concurrency queue. The API runs on a free Render instance, so
     * firing 20+ parallel requests from a busy topic page is a reliable way to
     * get slow responses or rate limiting.
     */
    const queue = [];
    let active = 0;

    function enqueue(task) {
        return new Promise((resolve, reject) => {
            queue.push({ task, resolve, reject });
            pump();
        });
    }

    function pump() {
        while (active < MAX_CONCURRENCY && queue.length > 0) {
            const { task, resolve, reject } = queue.shift();

            active += 1;

            task().then(resolve, reject).finally(() => {
                active -= 1;
                pump();
            });
        }
    }

    // uid -> in-flight promise, so the same poster appearing in several posts
    // on one page results in exactly one network request.
    const inFlight = new Map();

    /**
     * Interprets an API response.
     *
     * Verified against the live API:
     *   200 -> { user_id, score_band, message, improvements[] }
     *   404 -> user exists but has no score / unknown user
     *   422 -> non-numeric uid (should not happen, uid is regex-validated)
     * A 404 is a definitive "no score", so it is cached rather than retried.
     */
    function interpret(response) {
        if (response.status === 404) {
            return { ok: true, score: null };
        }

        if (response.status === 422) {
            return { ok: false, score: null, error: "Invalid user id", retry: false };
        }

        if (response.status < 200 || response.status >= 300) {
            // 5xx and 429 are worth one retry; other 4xx are not.
            const retry = response.status >= 500 || response.status === 429;
            return {
                ok: false,
                score: null,
                error: "Server returned HTTP " + response.status,
                retry
            };
        }

        let data;

        try {
            data = JSON.parse(response.responseText);
        } catch (e) {
            return { ok: false, score: null, error: "Malformed JSON from API", retry: false };
        }

        const band = data && data.score_band;

        return {
            ok: true,
            score: typeof band === "string" && band.trim() ? band.trim() : null,
            message: data && typeof data.message === "string" ? data.message : "",
            improvements: data && Array.isArray(data.improvements) ? data.improvements : []
        };
    }

    async function fetchScoreOnce(uid) {
        const url = API_ROOT + "/score/" + encodeURIComponent(uid);
        const response = await httpGet(url);
        return interpret(response);
    }

    async function fetchScoreWithRetry(uid) {
        // A missing GM request API is a configuration problem, not a transient
        // one, so fail immediately rather than sleeping through pointless retries.
        if (!requester) {
            return {
                ok: false,
                score: null,
                error: "No cross-origin request API available. This script needs " +
                    "@grant GM_xmlhttpRequest (or GM.xmlHttpRequest). Try " +
                    "reinstalling it."
            };
        }

        let last = { ok: false, score: null, error: "Unknown error" };

        for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {
            try {
                const result = await fetchScoreOnce(uid);

                if (result.ok) return result;

                last = result;

                if (result.retry === false) return result;
            } catch (e) {
                last = {
                    ok: false,
                    score: null,
                    error: String(e && e.message || e)
                };
            }

            if (attempt < MAX_RETRIES) await sleep(RETRY_DELAY_MS * (attempt + 1));
        }

        return last;
    }

    /**
     * Cache-first score lookup. Resolves (never rejects) with
     * { ok, score, cached, error?, message?, improvements? }.
     */
    function getScore(uid) {
        const cached = cacheGet(uid);

        if (cached) {
            return Promise.resolve({ ok: true, score: cached.score, cached: true });
        }

        if (inFlight.has(uid)) return inFlight.get(uid);

        const promise = enqueue(() => fetchScoreWithRetry(uid))
            .then(result => {
                if (result.ok) cachePut(uid, result.score);
                return Object.assign({ cached: false }, result);
            })
            .catch(e => ({
                ok: false,
                score: null,
                cached: false,
                error: String(e && e.message || e)
            }))
            .finally(() => {
                inFlight.delete(uid);
            });

        inFlight.set(uid, promise);

        return promise;
    }

    // ---------------------------------------------------------- presentation

    const COLOR_TOP_TIER = "#3465A4";
    const COLOR_GOOD = "#28a745";
    const COLOR_BAD = "#dc3545";
    const COLOR_NEUTRAL = "#777";

    /**
     * Turns a raw score_band string into display values.
     *
     * Live API example: "Good (Top 25% of active posters)"
     * The grade is the part before the parenthesis, the detail is inside it.
     */
    function scorePresentation(score, message) {
        if (!score) {
            return {
                text: "Score not met",
                color: COLOR_NEUTRAL,
                title: "A Post Health Score is not available for this account.",
                bold: false
            };
        }

        let grade = score;
        let detail = "";
        let percentage = "";
        let color = "#555";

        const match = score.match(/^(.*?)\s*\((.*?)\)\s*$/);

        if (match) {
            grade = match[1].trim();
            detail = match[2].trim();

            const percentMatch = detail.match(/(\d+)\s*%/);

            if (percentMatch) {
                percentage = percentMatch[1] + "%";

                const value = parseInt(percentMatch[1], 10);
                const isTop = /\btop\b/i.test(detail);

                if (isTop) {
                    if (value <= 10) color = COLOR_TOP_TIER;
                    else if (value <= 50) color = COLOR_GOOD;
                    else color = COLOR_BAD;
                } else {
                    color = COLOR_BAD;
                }
            }
        }

        // Fall back to keyword matching when there is no usable percentage,
        // covering bands phrased without a parenthetical.
        if (!percentage) {
            const lower = grade.toLowerCase();

            if (/excellent|great|good|healthy/.test(lower)) color = COLOR_GOOD;
            else if (/poor|bad|low|unhealthy|spam/.test(lower)) color = COLOR_BAD;
        }

        const titleParts = [detail || score];
        if (message) titleParts.push(message);

        return {
            text: percentage ? percentage + " - " + grade : grade,
            color,
            title: titleParts.join(" \u2014 "),
            bold: true
        };
    }

    // ------------------------------------------------------------- DOM layer

    function getUid(cell) {
        // Bitcointalk profile links use SMF's `;`-delimited query strings,
        // e.g. index.php?action=profile;u=101872
        const link = cell.querySelector('a[href*="action=profile"][href*="u="]');

        if (!link) return null;

        const href = link.getAttribute("href") || link.href || "";
        const match = href.match(/[?;&]u=(\d+)/);

        return match ? match[1] : null;
    }

    function createBadge() {
        const badge = document.createElement("span");
        badge.className = BADGE_CLASS;

        const label = document.createElement("span");
        label.className = "bth-label";
        label.textContent = "Score: ";

        const value = document.createElement("span");
        value.className = "bth-value bth-loading";
        value.textContent = "Loading\u2026";

        badge.appendChild(document.createElement("br"));
        badge.appendChild(label);
        badge.appendChild(value);

        return badge;
    }

    /**
     * Finds the text node holding the Posts/Merit/Activity stats so the badge
     * can be placed directly after it. Prefers "Posts:", then "Merit:", then
     * "Activity:", matching the visual order Bitcointalk uses.
     */
    function findAnchorNode(small) {
        let posts = null;
        let merit = null;
        let activity = null;

        for (const node of small.childNodes) {
            if (node.nodeType !== Node.TEXT_NODE) continue;

            const text = node.textContent || "";

            if (!posts && text.includes("Posts:")) posts = node;
            if (!merit && text.includes("Merit:")) merit = node;
            if (!activity && text.includes("Activity:")) activity = node;
        }

        return posts || merit || activity;
    }

    function renderResult(badge, uid, result) {
        const value = badge.querySelector(".bth-value");

        if (!value) return;

        if (!result.ok) {
            value.classList.remove("bth-loading");
            value.classList.add("bth-error");
            value.textContent = "Unavailable";
            value.title = "Could not load the Post Health Score: " +
                (result.error || "unknown error") +
                ". The API may be starting up \u2014 reload the page in a moment.";
            return;
        }

        const info = scorePresentation(result.score, result.message);

        const link = document.createElement("a");
        link.className = "bth-value bth-link";
        link.href = result.score
            ? API_ROOT + "/?uid=" + encodeURIComponent(uid)
            : API_ROOT + "/";
        link.target = "_blank";
        link.rel = "noopener noreferrer";
        link.textContent = info.text;
        link.title = info.title;
        link.style.color = info.color;
        link.style.fontWeight = info.bold ? "600" : "400";

        value.replaceWith(link);
    }

    function insertBadge(cell, uid) {
        if (cell.querySelector("." + BADGE_CLASS)) return;

        const small = cell.querySelector(".smalltext");
        if (!small) return;

        const badge = createBadge();
        const anchor = findAnchorNode(small);

        if (anchor && anchor.parentNode === small) {
            small.insertBefore(badge, anchor.nextSibling);
        } else {
            small.appendChild(badge);
        }

        getScore(uid).then(result => renderResult(badge, uid, result));
    }

    function processPage() {
        const cells = document.querySelectorAll("td.poster_info");

        log("poster cells:", cells.length);

        cells.forEach(cell => {
            const uid = getUid(cell);

            if (!uid) return;

            insertBadge(cell, uid);
        });
    }

    // ---------------------------------------------------------------- styles

    /**
     * Injects the stylesheet. GM_addStyle is deliberately not used: it is
     * absent in Greasemonkey 4 and would need another grant, whereas a plain
     * <style> element behaves identically in all three managers.
     */
    function addStyles() {
        if (document.getElementById(STYLE_ID)) return;

        const style = document.createElement("style");
        style.id = STYLE_ID;
        style.textContent = [
            "." + BADGE_CLASS + " {",
            "    font-size: 12px;",
            "    line-height: 1.45;",
            "    font-weight: 600;",
            "}",
            "." + BADGE_CLASS + " .bth-loading {",
            "    color: #777;",
            "    font-weight: 400;",
            "}",
            "." + BADGE_CLASS + " .bth-error {",
            "    color: #777;",
            "    font-weight: 400;",
            "    cursor: help;",
            "}",
            "." + BADGE_CLASS + " .bth-link {",
            "    text-decoration: none;",
            "}",
            "." + BADGE_CLASS + " .bth-link:hover {",
            "    text-decoration: underline;",
            "}",
            "@media (max-width: 600px) {",
            "    ." + BADGE_CLASS + " { font-size: 11px; }",
            "}"
        ].join("\n");

        (document.head || document.documentElement).appendChild(style);
    }

    // ------------------------------------------------------------- bootstrap

    /**
     * Only topic pages carry poster info cells. This is checked in JS as well
     * as via @match because @match cannot express query-string conditions.
     */
    function isTopicPage() {
        try {
            const url = new URL(location.href);

            if (!/(^|\.)bitcointalk\.org$/i.test(url.hostname)) return false;
            if (url.pathname !== "/index.php") return false;

            // Topic pages are ?topic=123 or ?topic=123.20; SMF also accepts
            // the `;`-delimited form, which URLSearchParams does not split.
            if (url.searchParams.has("topic")) return true;

            return /[?;&]topic=\d+/.test(url.search);
        } catch (e) {
            return false;
        }
    }

    let debounceTimer = null;

    function start() {
        addStyles();

        // The cache load is awaited before the first pass so that a warm cache
        // renders without any network request at all.
        loadCache().then(processPage);

        const observer = new MutationObserver(mutations => {
            // Ignore mutations caused by our own badge insertions.
            const relevant = mutations.some(m => {
                for (const node of m.addedNodes) {
                    if (node.nodeType !== Node.ELEMENT_NODE) continue;
                    if (node.classList && node.classList.contains(BADGE_CLASS)) continue;
                    if (node.id === STYLE_ID) continue;
                    return true;
                }
                return false;
            });

            if (!relevant) return;

            if (debounceTimer !== null) clearTimeout(debounceTimer);
            debounceTimer = setTimeout(processPage, 500);
        });

        if (document.body) {
            observer.observe(document.body, { childList: true, subtree: true });
        }

        // Flush any pending cache write before the page goes away, otherwise a
        // quick navigation loses everything learned on this page.
        window.addEventListener("pagehide", () => {
            if (flushTimer === null) return;

            clearTimeout(flushTimer);
            flushTimer = null;
            pruneCache();

            const plain = {};
            for (const [uid, entry] of cache) plain[uid] = entry;

            store.set(STORAGE_KEY, JSON.stringify(plain))
                .catch(() => { /* the page is closing; nothing to recover */ });
        });

        log("started via", requester ? requester.name : "no requester",
            "and", store.name);
    }

    if (isTopicPage()) {
        start();
    } else {
        log("not a topic page, idle");
    }
})();


Output

███████▄▄▀▀▀▀▀▄▄▄▄██████▄▄▀▀████▀▀▄▄
████▄▄▀▄▄████▄██▄▄▄█████▀▀▀▀█████
██▄▀▄▄██▀▀███▄▀██████▀▄▄██████▄██▄█
▄█▄▄▀▀████▄▄▀███████▄██████████▌████
█▀██▀▀▀▀███▄▄██████▐██████████████
▐▌█████▄▄▀█▀▀█████████████████▌███
█████▀▀▐▌████████▐█████████▀
██████████████████▄████████▀▄███▄▀
███████▄▄█████▄▀██▀▀▀▀▀███▀████▀
████▀▄▄▄▄▀▀███▄▄▄▀▄██▄▄▄▄███▄▄▀▀█▀▄█
█████████▀▀▀▀▀▀████▀▀████▀▀▀▄▄▄▀▄█▀
██▄▄▀▀▀▀▀▀██████▄▄▄██▀▀▀▀▀▀▀▄▄▄██▀
▄▀█████████████▀▀▀███████████▀▀▀
████
██
██
██
██
██
██
██
██
██
██
██
████
 

🛡️

📱
 
INSTANT TRANSACTIONS
SECURE & TRUSTWORTHY
24/7 ENTERTAINMENTS
PLAY ON ANY DEVICE
████
██
██
██
██
██
██
██
██
██
██
██
████
██
██
██
██
██
██
██
██
██
██
██
██
██
 
 $20 
██
██
██
██
██
██
██
██
██
██
██
██
██
 
  PLAY NOW  
Upgrade00
Legendary
*
Offline

Activity: 2856
Merit: 2944


Community Manager - Brand Promotions ✅


View Profile WWW
August 27, 2026, 06:06:01 PM
Merited by Mia Chloe (2)
 #2

You improve your post quality by reading more on your topics of interest and putting more effort into posts/replies you make. If you need help with communication there are threads on proper writing etiquette that will help you better pass the message in your writings.

███████████████████████████
███████▄████████████▄██████
████████▄████████▄████████
███▀█████▀▄███▄▀█████▀███
█████▀█▀▄██▀▀▀██▄▀█▀█████
███████▄███████████▄███████
███████████████████████████
███████▀███████████▀███████
████▄██▄▀██▄▄▄██▀▄██▄████
████▄████▄▀███▀▄████▄████
██▄███▀▀█▀██████▀█▀███▄███
██▀█▀████████████████▀█▀███
███████████████████████████
.
.Duelbits..REWARDING, BEYOND LIMITS...
█████████████████████████
█████████████████████████
███████████▀▀░░▀█▄░░▀████
████████▀░░░░░░░░▀█▄░████
███████░░░░▄▄░░▄░░░▀█████
██████░░░░░▀▀▄██▀░░░░████
█████░░░██░▄██▀▄▄░░░█████
████░░░░░▄██▀░░▀▀░░██████
█████▄░░▀█▀░██░░░░███████
████░▀█▄░░░░░░░░▄████████
████▄░░▀█▄░░▄▄███████████
█████████████████████████
█████████████████████████
█████████████████████████
█████████████████████████
█████████▀░░▀░███████████
████████░░░▄░█░██████████
███████████▌▐██░█████████
███████████░███▌▐████████
██████████░█████░████████
██████▀░▄░▀███▀░▄░▀█████
█████░▄▀░░░░█░▄▀░░░░█████
█████░░░░░░░█░░░░░░░█████
██████▄░░░▄███▄░░░▄██████
█████████████████████████
█████████████████████████


























  PLAY NOW  
DYING_S0UL
Legendary
*
Offline

Activity: 1134
Merit: 1201


The Alliance Of Bitcointalk Translator - AOBT


View Profile WWW
August 27, 2026, 06:15:46 PM
Last edit: August 27, 2026, 06:27:38 PM by DYING_S0UL
 #3

Nice one! Btw, similar script/extension already exists. My bad I didn't notice the first paragraph, it seems you already took the inspiration from masulum's tool.  Lips sealed

Posts Health Score Extension by masulum.
https://github.com/gakjahat/bitcointalkposthealth

Bug:
There is a slight bug in your version though. When I click the score, I am redirected to the original site where I should see detailed explanations.
But it ain't showing that. The link seems fine, the UID and everything, but still there is no explanation how it's supposed to be. You better check what went wrong.
On the other tool, it shows everything properly. You don't need to manually add the UID, it does everything on it's own when you press on the score.

I'm on Brave.

Now if I hover around the score, I can however see a short version of the explanation, but that's not everything.

What your userscript shows:


What it's supposed to show (ss from the other tool):


▄▄███████████████████▄▄
▄███████████████████████▄
████████████████████████
█████████████████████████
████████████████████████
████████████▀██████▀████
████████████████████████
█████████▄▄▄▄███████████
██████████▄▄▄████████████
████████████████████████
████████████████▀▀███████
▀███████████████████████▀
▀▀███████████████████▀▀
 
 EARNBET 
██
██
██
██
██
██
██
██
██
██
██
██
██
███████▄▄███████████
████▄██████████████████
██▀▀███████████████▀▀███
▄████████████████████████
▄▄████████▀▀▀▀▀████████▄▄██
███████████████████████████
█████████▌██▀████████████
███████████████████████████
▀▀███████▄▄▄▄▄█████████▀▀██
▀█████████████████████▀██
██▄▄███████████████▄▄███
████▀██████████████████
███████▀▀███████████
██
██
██
██
██
██
██
██
██
██
██
██
██


▄▄▄
▄▄▄███████▐███▌███████▄▄▄
█████████████████████████
▀████▄▄▄███████▄▄▄████▀
█████████████████████
▐███████████████████▌
███████████████████
███████████████████
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀

 King of The Castle 
 $200,000 in prizes
██
██
██
██
██
██
██
██
██
██
██
██
██

 62.5% 

 
RAKEBACK
BONUS
Z_MBFM (OP)
Hero Member
*****
Online Online

Activity: 1218
Merit: 503



View Profile WWW
August 27, 2026, 06:36:56 PM
 #4

Bug:
There is a slight bug in your version though. When I click the score, I am redirected to the original site where I should see detailed explanations.
But it ain't showing that. The link seems fine, the UID and everything, but still there is no explanation how it's supposed to be. You better check what went wrong.
On the other tool, it shows everything properly. You don't need to manually add the UID, it does everything on it's own when you press on the score.

What it's supposed to show (ss from the other tool):

This is not a bug from my side. This bug is the main site of this site https://postflayer.onrender.com. You enter this site normally without using the extension and check the score with a UID. Then copy the link that you get in the search URL and open it in another tab, then you will not see the score anymore, you will have to input the UID again. So to solve this bug, it will be necessary to fix the bug of the main site which create nutildah

███████▄▄▀▀▀▀▀▄▄▄▄██████▄▄▀▀████▀▀▄▄
████▄▄▀▄▄████▄██▄▄▄█████▀▀▀▀█████
██▄▀▄▄██▀▀███▄▀██████▀▄▄██████▄██▄█
▄█▄▄▀▀████▄▄▀███████▄██████████▌████
█▀██▀▀▀▀███▄▄██████▐██████████████
▐▌█████▄▄▀█▀▀█████████████████▌███
█████▀▀▐▌████████▐█████████▀
██████████████████▄████████▀▄███▄▀
███████▄▄█████▄▀██▀▀▀▀▀███▀████▀
████▀▄▄▄▄▀▀███▄▄▄▀▄██▄▄▄▄███▄▄▀▀█▀▄█
█████████▀▀▀▀▀▀████▀▀████▀▀▀▄▄▄▀▄█▀
██▄▄▀▀▀▀▀▀██████▄▄▄██▀▀▀▀▀▀▀▄▄▄██▀
▄▀█████████████▀▀▀███████████▀▀▀
████
██
██
██
██
██
██
██
██
██
██
██
████
 

🛡️

📱
 
INSTANT TRANSACTIONS
SECURE & TRUSTWORTHY
24/7 ENTERTAINMENTS
PLAY ON ANY DEVICE
████
██
██
██
██
██
██
██
██
██
██
██
████
██
██
██
██
██
██
██
██
██
██
██
██
██
 
 $20 
██
██
██
██
██
██
██
██
██
██
██
██
██
 
  PLAY NOW  
DYING_S0UL
Legendary
*
Offline

Activity: 1134
Merit: 1201


The Alliance Of Bitcointalk Translator - AOBT


View Profile WWW
August 27, 2026, 06:48:56 PM
Merited by Z_MBFM (1)
 #5

This is not a bug from my side. This bug is the main site of this site https://postflayer.onrender.com. You enter this site normally without using the extension and check the score with a UID. Then copy the link that you get in the search URL and open it in another tab, then you will not see the score anymore, you will have to input the UID again. So to solve this bug, it will be necessary to fix the bug of the main site which create nutildah

When I enter the site without any tool, manually input an UID, the link does not change. It shows as (https://postflayer.onrender.com/) and the msg. There are no "?uid=123456" suffix.

With the extension on (not yours), when I click on the score, I can clearly see the explanation message. Even after copying that link and pasting it on another fresh tab, the message is visible. So I don't understand what's the problem here.!

Anyway, the point is with your userscript I cannot see the explanations message, it takes me to the main site but I still need to input the UID manually. But with masulum's tool, whichever road/path/method I take, I could always see the message, with only 1 click. So figure it out, why his tool works, and why yours doesn't. (I'm not criticizing you or anything) Roll Eyes

▄▄███████████████████▄▄
▄███████████████████████▄
████████████████████████
█████████████████████████
████████████████████████
████████████▀██████▀████
████████████████████████
█████████▄▄▄▄███████████
██████████▄▄▄████████████
████████████████████████
████████████████▀▀███████
▀███████████████████████▀
▀▀███████████████████▀▀
 
 EARNBET 
██
██
██
██
██
██
██
██
██
██
██
██
██
███████▄▄███████████
████▄██████████████████
██▀▀███████████████▀▀███
▄████████████████████████
▄▄████████▀▀▀▀▀████████▄▄██
███████████████████████████
█████████▌██▀████████████
███████████████████████████
▀▀███████▄▄▄▄▄█████████▀▀██
▀█████████████████████▀██
██▄▄███████████████▄▄███
████▀██████████████████
███████▀▀███████████
██
██
██
██
██
██
██
██
██
██
██
██
██


▄▄▄
▄▄▄███████▐███▌███████▄▄▄
█████████████████████████
▀████▄▄▄███████▄▄▄████▀
█████████████████████
▐███████████████████▌
███████████████████
███████████████████
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀

 King of The Castle 
 $200,000 in prizes
██
██
██
██
██
██
██
██
██
██
██
██
██

 62.5% 

 
RAKEBACK
BONUS
Mia Chloe
Legendary
*
Offline

Activity: 1176
Merit: 2287


Contact me for your designs...


View Profile
August 27, 2026, 07:02:57 PM
Merited by Z_MBFM (1)
 #6

~snip
Nice one to start with it's actually good to at least see the effort people are generally putting in to ensure we can keep up quality posting. That being said most of these tools are not spot on in terms of overall statistics in my own opinion. The factors that determine post quality are actually variables not constants.

In the end to maintain quality is not really difficult so long you post when necessary. That aside what other specific information does the script use to grade user post quality aside from nuthilda's?

masulum
Legendary
*
Offline

Activity: 2842
Merit: 1973


NO DEPO CODE VEGAR7, NO KYC Casino


View Profile WWW
August 27, 2026, 08:19:00 PM
Merited by Z_MBFM (2), DYING_S0UL (1)
 #7

Anyway, the point is with your userscript I cannot see the explanations message, it takes me to the main site but I still need to input the UID manually. But with masulum's tool, whichever road/path/method I take, I could always see the message, with only 1 click. So figure it out, why his tool works, and why yours doesn't. (I'm not criticizing you or anything) Roll Eyes

my extension ran with some tweak to make autofill for postflayer.onrender.com, when users clicked the score the autofill run in the background. After the pages fully loaded the UI ready with everything.
maybe OP can try to add that feature to make it automatic. you just need to add two features to solved that:
- add autofill to postflayer.onrender.com script
- DOM Manipulation script

in the end, the basic process will be users clicked the score > open in new tab > DOM Manipulation to fill the UID form > postflayer.onrender.com load >  postflayer.onrender.com UI ready with uid filled.

hopefully it can be implemented in the next your script update.

██████
██
██

████████████████
███████████████
█████████████
█████████████▄▄████▄▄████▄▄███████▌██▄▄████▄██
████████████▄██▀▀▀▀██▄██▄███▀███████▄██▀▀▀▀███
██████████▐██▄▄▄▄▄▄██▌▐██▀███████▌▐███████▐██
████████████▐██▀▀▀▀▀▀▀▀▐██▄███████▌▐██▄████▐██
█████████████▀██▄▄▄▄█████▀███▄▄▄██▀██▀██▄▄▄▄███
██████████████▀▀▀▀▀▀██████▀▀▀▀▀▀▄▌███▀▀▀▀▀▀▀
████████████████████████████▄███▄██
███████████████████████████▀█████▀










██
██
██████
▄▄███████▄▄
▄███████████████▄
▄███████████████████▄
▄█████████████████████▄
▄███████████████████████
████████████████████████
█████████████████████████
████████████████████████
▀███████████████████████▀
█████████████████████▀
▀███████████████████▀
▀███████████████▀
▀▀███████▀▀
 
  150 FS NO DEPOSIT BONUS ..... Subscribe to Our Telegram ( > ) .....   PLAY NOW   
dkbit98
Legendary
*
Offline

Activity: 3066
Merit: 8839



View Profile WWW
August 27, 2026, 08:21:47 PM
Merited by Z_MBFM (1)
 #8

I tested your health script and something in your math calculations is not feeling right to me.
There are some members I won't name having elite poster score, and I know them for writing low quality content.
I am going to test it more in next few days, and I will show how silly this health thing is.

▄▄██████▄░░░▄██████▄▄
██▀▀░░░░░░░░░░░░░▀▀██
▄▄██████▄▄██████▄▄
▄████▀▀▀▀█████▀▀▀▀████▄
▄███░░░▄▄░░░░░░▄▄░░░███▄
▄▄▄███░░░░██░░░░░░░██░░░░███▄▄▄
████████░░░░██░░░░░░░██░░░░████████
██████████░░░▀▀░░░░░░▀▀░░░██████████
████▀▀██████▄▄▄▄█████▄▄▄▄██████▀▀████
▀███▄░░▀▀███████████████████▀▀░░▄███▀
▀████▄▄░░░░▀▀▀▀▀▀▀▀▀▀▀▀▀░░░░▄▄████▀
▀███████▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄███████▀
▀▀█████████████████████▀▀
  
OrangeFren
  
██
██
██
██
██
██
██
██
██
██
██
  
▄▄█████▄▄
▄████▀▀▀████▄
███▀░░░░░░░▀███
███▀░░░▄█░░░░▀███
███░░░░░░░░░░███
███▄░░░▄█▄░░░▄███
███▄░░░░░░░▄███
▀████▄▄▄████▀
█████████
▐█████████▌
██████████
▐████▌▐████▌
▀▀▀█░░░█▀▀▀
 
Z_MBFM (OP)
Hero Member
*****
Online Online

Activity: 1218
Merit: 503



View Profile WWW
August 27, 2026, 09:55:24 PM
 #9

That aside what other specific information does the script use to grade user post quality aside from nuthilda's?
No, the script is not currently validating any information other than nutildah's information. However, I would appreciate everyone's suggestions and update the script.

Anyway, the point is with your userscript I cannot see the explanations message, it takes me to the main site but I still need to input the UID manually. But with masulum's tool, whichever road/path/method I take, I could always see the message, with only 1 click. So figure it out, why his tool works, and why yours doesn't. (I'm not criticizing you or anything) Roll Eyes

my extension ran with some tweak to make autofill for postflayer.onrender.com, when users clicked the score the autofill run in the background. After the pages fully loaded the UI ready with everything.
maybe OP can try to add that feature to make it automatic. you just need to add two features to solved that:
- add autofill to postflayer.onrender.com script
- DOM Manipulation script

in the end, the basic process will be users clicked the score > open in new tab > DOM Manipulation to fill the UID form > postflayer.onrender.com load >  postflayer.onrender.com UI ready with uid filled.

hopefully it can be implemented in the next your script update.
I appreciate your recommendation. I will definitely update my script and implement this. This issue will be fixed in the next update.

I tested your health script and something in your math calculations is not feeling right to me.
There are some members I won't name having elite poster score, and I know them for writing low quality content.
I am going to test it more in next few days, and I will show how silly this health thing is.
Thank you very much, I will definitely look forward to your advice. Currently, the status is being shown from postflayer, so the way the score calculation is being done there according to the postflayer calculation system is considered effective.

███████▄▄▀▀▀▀▀▄▄▄▄██████▄▄▀▀████▀▀▄▄
████▄▄▀▄▄████▄██▄▄▄█████▀▀▀▀█████
██▄▀▄▄██▀▀███▄▀██████▀▄▄██████▄██▄█
▄█▄▄▀▀████▄▄▀███████▄██████████▌████
█▀██▀▀▀▀███▄▄██████▐██████████████
▐▌█████▄▄▀█▀▀█████████████████▌███
█████▀▀▐▌████████▐█████████▀
██████████████████▄████████▀▄███▄▀
███████▄▄█████▄▀██▀▀▀▀▀███▀████▀
████▀▄▄▄▄▀▀███▄▄▄▀▄██▄▄▄▄███▄▄▀▀█▀▄█
█████████▀▀▀▀▀▀████▀▀████▀▀▀▄▄▄▀▄█▀
██▄▄▀▀▀▀▀▀██████▄▄▄██▀▀▀▀▀▀▀▄▄▄██▀
▄▀█████████████▀▀▀███████████▀▀▀
████
██
██
██
██
██
██
██
██
██
██
██
████
 

🛡️

📱
 
INSTANT TRANSACTIONS
SECURE & TRUSTWORTHY
24/7 ENTERTAINMENTS
PLAY ON ANY DEVICE
████
██
██
██
██
██
██
██
██
██
██
██
████
██
██
██
██
██
██
██
██
██
██
██
██
██
 
 $20 
██
██
██
██
██
██
██
██
██
██
██
██
██
 
  PLAY NOW  
masulum
Legendary
*
Offline

Activity: 2842
Merit: 1973


NO DEPO CODE VEGAR7, NO KYC Casino


View Profile WWW
August 28, 2026, 05:36:18 AM
 #10

I appreciate your recommendation. I will definitely update my script and implement this. This issue will be fixed in the next update.

you are welcome bro, hopefully it will success to be implemented, so forum members have own choice to use wether to use extension or your script.



There are some members I won't name having elite poster score, and I know them for writing low quality content.
postflayer itself is not perfect yet, if you found an anomaly scrore from this script, try to visit postflayer and compare the results, if it different maybe it's a ug. but if the results is same with postflayer its mean not a bug from the script but postflayer data giving wrong information because not loaded yet or because another reason. as mentioned by OP, my extension or this script just injected bitcointalk pages with score from postflayer, not having own scoring system.

██████
██
██

████████████████
███████████████
█████████████
█████████████▄▄████▄▄████▄▄███████▌██▄▄████▄██
████████████▄██▀▀▀▀██▄██▄███▀███████▄██▀▀▀▀███
██████████▐██▄▄▄▄▄▄██▌▐██▀███████▌▐███████▐██
████████████▐██▀▀▀▀▀▀▀▀▐██▄███████▌▐██▄████▐██
█████████████▀██▄▄▄▄█████▀███▄▄▄██▀██▀██▄▄▄▄███
██████████████▀▀▀▀▀▀██████▀▀▀▀▀▀▄▌███▀▀▀▀▀▀▀
████████████████████████████▄███▄██
███████████████████████████▀█████▀










██
██
██████
▄▄███████▄▄
▄███████████████▄
▄███████████████████▄
▄█████████████████████▄
▄███████████████████████
████████████████████████
█████████████████████████
████████████████████████
▀███████████████████████▀
█████████████████████▀
▀███████████████████▀
▀███████████████▀
▀▀███████▀▀
 
  150 FS NO DEPOSIT BONUS ..... Subscribe to Our Telegram ( > ) .....   PLAY NOW   
Z_MBFM (OP)
Hero Member
*****
Online Online

Activity: 1218
Merit: 503



View Profile WWW
August 28, 2026, 08:08:31 AM
 #11

Script has been updateded 3.2.0 (All Bug Has been solved)

Install or Update Directly - https://greasyfork.org/en/scripts/593305-bitcointalk-post-health

Output

███████▄▄▀▀▀▀▀▄▄▄▄██████▄▄▀▀████▀▀▄▄
████▄▄▀▄▄████▄██▄▄▄█████▀▀▀▀█████
██▄▀▄▄██▀▀███▄▀██████▀▄▄██████▄██▄█
▄█▄▄▀▀████▄▄▀███████▄██████████▌████
█▀██▀▀▀▀███▄▄██████▐██████████████
▐▌█████▄▄▀█▀▀█████████████████▌███
█████▀▀▐▌████████▐█████████▀
██████████████████▄████████▀▄███▄▀
███████▄▄█████▄▀██▀▀▀▀▀███▀████▀
████▀▄▄▄▄▀▀███▄▄▄▀▄██▄▄▄▄███▄▄▀▀█▀▄█
█████████▀▀▀▀▀▀████▀▀████▀▀▀▄▄▄▀▄█▀
██▄▄▀▀▀▀▀▀██████▄▄▄██▀▀▀▀▀▀▀▄▄▄██▀
▄▀█████████████▀▀▀███████████▀▀▀
████
██
██
██
██
██
██
██
██
██
██
██
████
 

🛡️

📱
 
INSTANT TRANSACTIONS
SECURE & TRUSTWORTHY
24/7 ENTERTAINMENTS
PLAY ON ANY DEVICE
████
██
██
██
██
██
██
██
██
██
██
██
████
██
██
██
██
██
██
██
██
██
██
██
██
██
 
 $20 
██
██
██
██
██
██
██
██
██
██
██
██
██
 
  PLAY NOW  
MisFoxie
Full Member
***
Offline

Activity: 254
Merit: 121



View Profile
August 28, 2026, 10:13:52 AM
 #12

In the new update your tool working perfectly. The feature to see details without clicking external link or leaving the forum is what i like the most. Although I didn't find any bugs but if I ever find one I will definitely let you know.


That is why I made this script so that all users can use it easily. To improve post quality, it is important to monitor your post health score and the post health score of others, it will be easy to analyze the posts of those who are posting well and improve your own posts. So I think this script should be installed by everyone.
I don't think users can use this tool to improve themselve right now because postflayer didn't update their data after the launch. I hope they continuously update their data so that user can see their improvement.

DYING_S0UL
Legendary
*
Offline

Activity: 1134
Merit: 1201


The Alliance Of Bitcointalk Translator - AOBT


View Profile WWW
August 28, 2026, 02:03:50 PM
 #13

Script has been updateded 3.2.0 (All Bug Has been solved)

Install or Update Directly - https://greasyfork.org/en/scripts/593305-bitcointalk-post-health



I just tested it inside violetmonkey. And this time it worked flawlessly mate. And the fact that you added the whole message beside the profile information makes it even more useful.
Since I don't need to visit the "postflayer" site directly, I hope the message showing is the right one.

So far so good, no visible bug from my end. I would say an A+ job!  Smiley

▄▄███████████████████▄▄
▄███████████████████████▄
████████████████████████
█████████████████████████
████████████████████████
████████████▀██████▀████
████████████████████████
█████████▄▄▄▄███████████
██████████▄▄▄████████████
████████████████████████
████████████████▀▀███████
▀███████████████████████▀
▀▀███████████████████▀▀
 
 EARNBET 
██
██
██
██
██
██
██
██
██
██
██
██
██
███████▄▄███████████
████▄██████████████████
██▀▀███████████████▀▀███
▄████████████████████████
▄▄████████▀▀▀▀▀████████▄▄██
███████████████████████████
█████████▌██▀████████████
███████████████████████████
▀▀███████▄▄▄▄▄█████████▀▀██
▀█████████████████████▀██
██▄▄███████████████▄▄███
████▀██████████████████
███████▀▀███████████
██
██
██
██
██
██
██
██
██
██
██
██
██


▄▄▄
▄▄▄███████▐███▌███████▄▄▄
█████████████████████████
▀████▄▄▄███████▄▄▄████▀
█████████████████████
▐███████████████████▌
███████████████████
███████████████████
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀

 King of The Castle 
 $200,000 in prizes
██
██
██
██
██
██
██
██
██
██
██
██
██

 62.5% 

 
RAKEBACK
BONUS
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!