Surlignage Post

Version de test. Met en évidence l'auteur (OP), les citations, les PEMT, et vos propres messages sur tous les forums JVC.

当前为 2025-10-04 提交的版本,查看 最新版本

// ==UserScript==
// @name         Surlignage Post
// @namespace    http://tampermonkey.net/
// @version      0.8.3
// @description  Version de test. Met en évidence l'auteur (OP), les citations, les PEMT, et vos propres messages sur tous les forums JVC.
// @author       FaceDePet
// @match        *://www.jeuxvideo.com/forums/*
// @grant        GM_xmlhttpRequest
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    class JVCTopicEnhancer {
        constructor() {
            this.CACHE_KEY_OP = 'jvcTopicEnhancerCacheOP';
            this.CACHE_KEY_POSTS = 'jvcUserPostsCache';
            this.MAX_CACHED_POSTS_PER_TOPIC = 20;
            this.pemtColors = ['#1abc9c', '#3498db', '#9b59b6', '#e67e22', '#e74c3c'];
            this.init();
        }

        async init() {
            this.topicId = this.getTopicId();
            if (!this.topicId) {
                console.error("JVC Enhancer: Impossible de trouver l'ID du topic sur cette URL.");
                return;
            }

            this.currentPage = this.getCurrentPageNumber();
            this.currentUser = this.getCurrentUsername();

            this.addPostListener();

            try {
                const [opUsername, userMessages] = await Promise.all([
                    this.getOpUsername(),
                    this.currentUser ? this.getAllUserMessageContents() : []
                ]);
                this.opUsername = opUsername;
                this.currentUserMessages = userMessages;
            } catch (error) {
                console.error("JVC Enhancer: Erreur lors de la récupération des données.", error);
                return;
            }

            this.highlightRules = [
                { name: 'Original Poster', condition: (author) => author === this.opUsername },
                { name: 'User Mention', condition: (author, el) => this.isUserMentioned(author, el) },
                { name: 'Self', condition: (author) => author === this.currentUser }
            ];

            this.injectStyles();
            this.processMessages();
        }

        getTopicId() {
            // Regex assouplie pour fonctionner sur tous les forums
            const match = window.location.href.match(/\/forums\/\d+-\d+-(\d+)-/);
            return match ? match[1] : null;
        }

        getCurrentPageNumber() {
            const match = window.location.href.match(/-(\d+)-0-1-0-/);
            return match ? parseInt(match[1], 10) : 1;
        }

        async getOpUsername() {
            const cache = JSON.parse(sessionStorage.getItem(this.CACHE_KEY_OP)) || {};
            if (cache[this.topicId]) return cache[this.topicId];

            let newOpUsername = (this.currentPage === 1)
                ? this.parseOpUsernameFromDocument(document)
                : await this.fetchOpUsernameFromPageOne();

            if (newOpUsername) {
                cache[this.topicId] = newOpUsername;
                sessionStorage.setItem(this.CACHE_KEY_OP, JSON.stringify(cache));
            }
            return newOpUsername;
        }

        parseOpUsernameFromDocument(doc, isApi = false) {
            const selector = isApi ? '.post .bloc-pseudo-msg' : '.bloc-message-forum .bloc-pseudo-msg';
            return doc.querySelector(selector)?.textContent.trim() || null;
        }

        fetchOpUsernameFromPageOne() {
            return this.fetchPageContent(1).then(doc => this.parseOpUsernameFromDocument(doc, true));
        }

        fetchPageContent(pageNumber) {
            return new Promise((resolve, reject) => {
                const currentPattern = `-${this.currentPage}-0-1-0-`;
                const newPattern = `-${pageNumber}-0-1-0-`;

                const pageUrl = window.location.href.replace(new RegExp(currentPattern), newPattern);
                const apiUrl = pageUrl.replace('www.jeuxvideo.com', 'api.jeuxvideo.com');
                GM_xmlhttpRequest({
                    method: "GET",
                    url: apiUrl,
                    onload: (res) => resolve(new DOMParser().parseFromString(res.responseText, "text/html")),
                    onerror: (err) => reject(err)
                });
            });
        }

        async getAllUserMessageContents() {
            const cachedPosts = this.loadUserPostsFromCache();
            let pagePosts = this.parseMessagesFromDocument(document);
            let prevPagePosts = [];
            if (this.currentPage > 1) {
                try {
                    const prevPageDoc = await this.fetchPageContent(this.currentPage - 1);
                    prevPagePosts = this.parseMessagesFromDocument(prevPageDoc, true);
                } catch (e) { console.error("JVC Enhancer: Impossible de charger la page N-1.", e); }
            }
            const allMessages = [...cachedPosts, ...pagePosts, ...prevPagePosts];
            return [...new Set(allMessages)];
        }

        parseMessagesFromDocument(doc, isApi = false) {
            const messages = [];
            const sel = { msg: isApi ? '.post' : '.bloc-message-forum', pseudo: isApi ? '.bloc-pseudo-msg' : '.bloc-pseudo-msg', content: isApi ? '.message' : '.txt-msg' };
            doc.querySelectorAll(sel.msg).forEach(msg => {
                if (msg.querySelector(sel.pseudo)?.textContent.trim() === this.currentUser) {
                    const text = msg.querySelector(sel.content)?.textContent;
                    if (text) messages.push(this.normalizeText(text));
                }
            });
            return messages;
        }

        addPostListener() {
            const postButton = document.querySelector('.postMessage');
            if (postButton) {
                postButton.addEventListener('click', () => {
                    const messageTextarea = document.querySelector('#message_topic, .messageEditor__edit');
                    if (messageTextarea && messageTextarea.value && this.topicId) {
                        this.saveUserPostToCache(this.normalizeText(messageTextarea.value));
                    }
                }, true);
            }
        }

        saveUserPostToCache(normalizedText) {
            try {
                const cache = JSON.parse(localStorage.getItem(this.CACHE_KEY_POSTS)) || {};
                if (!cache[this.topicId]) cache[this.topicId] = [];
                cache[this.topicId].unshift(normalizedText);
                cache[this.topicId] = cache[this.topicId].slice(0, this.MAX_CACHED_POSTS_PER_TOPIC);
                localStorage.setItem(this.CACHE_KEY_POSTS, JSON.stringify(cache));
            } catch (e) { console.error("JVC Enhancer: Erreur de sauvegarde du cache.", e); }
        }

        loadUserPostsFromCache() {
            try {
                const cache = JSON.parse(localStorage.getItem(this.CACHE_KEY_POSTS)) || {};
                return cache[this.topicId] || [];
            } catch (e) { console.error("JVC Enhancer: Erreur de lecture du cache.", e); return []; }
        }

        isUserMentioned(author, el) {
            if (!this.currentUser || author === this.currentUser) return false;
            const content = el.querySelector('.txt-msg');
            if (!content) return false;
            const normText = this.normalizeText(content.textContent);
            if (normText.includes(this.normalizeText(this.currentUser))) return true;
            for (const quote of el.querySelectorAll('blockquote.blockquote-jv')) {
                const cleanQuote = this.normalizeText(quote.textContent.replace(/^Le .+? :/s, ''));
                if (cleanQuote && this.currentUserMessages.some(msg => msg.includes(cleanQuote))) return true;
            }
            return false;
        }

        normalizeText(text) {
            return text ? text.toLowerCase().replace(/\s+/g, ' ').trim() : '';
        }

        getCurrentUsername() {
            return document.querySelector('.headerAccount__pseudo')?.textContent.trim() || null;
        }

        injectStyles() {
            const styles = `
                .jvc-enhancer-op { /*background-color: rgba(0, 123, 255, 0.05);*/ /*border-left: 3px solid #007bff;*/ }
                .jvc-enhancer-mention { background-color: rgba(255, 193, 7, 0.07); border-left: 3px solid #ffc107; }
                .jvc-enhancer-op-mention { background-color: rgba(138, 43, 226, 0.07); border-left: 3px solid #8a2be2; }
                .jvc-enhancer-pemt { background-color: rgba(0, 0, 0, 0.03); border-left-width: 3px; border-left-style: solid; }
                .jvc-enhancer-self { box-shadow: inset 0 0 0 1px rgba(128, 128, 128, 0.2); }
                html:not(.theme-light) .jvc-enhancer-self { box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1); }

                .bloc-avatar-msg { position: relative; }
                .jvc-enhancer-avatar-badge {
                    position: absolute; bottom: -12px; left: 50%; transform: translateX(-50%); z-index: 2;
                    padding: 2px 6px; font-size: 9px; font-weight: bold; border-radius: 5px;
                    white-space: nowrap; text-transform: uppercase; letter-spacing: 0.5px;
                    backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
                    border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
                }

                .badge-op { background-color: rgba(128, 128, 128, 0.6); color: rgba(255, 255, 255, 0.8); /*background-color: rgba(0, 123, 255, 0.85); color: white;*/ }
                .badge-mention { background-color: rgba(255, 193, 7, 0.85); color: #212529; }
                .badge-op-mention { background-color: rgba(138, 43, 226, 0.85); color: white; }
                .badge-pemt { color: white; }
                .badge-self { background-color: rgba(128, 128, 128, 0.6); color: rgba(255, 255, 255, 0.8); }
            `;
            const styleSheet = document.createElement("style");
            styleSheet.innerText = styles;
            document.head.appendChild(styleSheet);
        }

        processMessages() {
            const messages = Array.from(document.querySelectorAll('.bloc-message-forum'));

            messages.forEach(el => {
                const author = el.querySelector('.bloc-pseudo-msg')?.textContent.trim();
                if (!author) return;

                const isOp = this.highlightRules[0].condition(author);
                const isMention = this.highlightRules[1].condition(author, el);
                const isSelf = this.highlightRules[2].condition(author);

                let hClass = '', bText = '', bClass = '';

                if (isSelf) {
                    hClass = 'jvc-enhancer-self'; bText = 'VOUS'; bClass = 'badge-self';
                }
                else if (isOp && isMention) {
                    hClass = 'jvc-enhancer-op-mention'; bText = 'OP (CITÉ)'; bClass = 'badge-op-mention';
                } else if (isOp) {
                    hClass = 'jvc-enhancer-op'; bText = 'OP'; bClass = 'badge-op';
                } else if (isMention) {
                    hClass = 'jvc-enhancer-mention'; bText = 'CITÉ'; bClass = 'badge-mention';
                }

                if (hClass) el.classList.add(hClass);
                if (bText) this.createAvatarBadge(el, bText, bClass);
            });

            let pemtGroupIndex = 0;
            for (let i = 0; i < messages.length - 1; i++) {
                const time = messages[i].querySelector('.bloc-date-msg a')?.textContent.match(/\d{2}:\d{2}:\d{2}$/)?.[0];
                if (!time) continue;

                const group = [messages[i]];
                for (let j = i + 1; j < messages.length; j++) {
                    if (messages[j].querySelector('.bloc-date-msg a')?.textContent.match(/\d{2}:\d{2}:\d{2}$/)?.[0] === time) {
                        group.push(messages[j]);
                    } else {
                        break;
                    }
                }

                if (group.length > 1) {
                    const color = this.pemtColors[pemtGroupIndex % this.pemtColors.length];
                    group.forEach(msg => {
                        msg.classList.add('jvc-enhancer-pemt');
                        msg.style.borderLeftColor = color;
                        if (!msg.querySelector('.jvc-enhancer-avatar-badge')) {
                            this.createAvatarBadge(msg, 'PEMT', 'badge-pemt', color);
                        }
                    });
                    pemtGroupIndex++;
                    i += group.length - 1;
                }
            }
        }

        createAvatarBadge(el, text, bClass, color = null) {
            const container = el.querySelector('.bloc-avatar-msg');
            if (!container) return;

            const badge = document.createElement('span');
            badge.className = `jvc-enhancer-avatar-badge ${bClass}`;
            badge.textContent = text;
            if (color) {
                badge.style.backgroundColor = `${color}D9`;
            }
            container.appendChild(badge);
        }
    }

    new JVCTopicEnhancer();
})();