// ==UserScript==
// @name Surlignage Post
// @namespace http://tampermonkey.net/
// @version 0.9.2
// @description Version de tests. Met en évidence l'auteur (OP), les citations, les PEMT, vos messages et les DDB sur tous les forums JVC.
// @author FaceDePet
// @match *://www.jeuxvideo.com/forums/*-*-*-*-*-*-*-*.htm
// @connect *.jeuxvideo.com
// @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.CACHE_KEY_DDB = 'jvcDdbStatusCache';
this.MAX_CACHED_POSTS_PER_TOPIC = 20;
this.pemtColors = ['#45a093', '#5c8fba', '#896a9e', '#c77e4d', '#c06158'];
this.init();
}
async init() {
this.topicId = this.getTopicId();
if (!this.topicId) 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();
this.checkFirstMessagesReportStatus();
}
getTopicId() { 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.fetchApiPageContent(1).then(doc => this.parseOpUsernameFromDocument(doc, true)); }
fetchApiPageContent(pageNumber) {
return new Promise((resolve, reject) => {
const url = window.location.href.replace(`-${this.currentPage}-0-1-0-`, `-${pageNumber}-0-1-0-`).replace('www.jeuxvideo.com', 'api.jeuxvideo.com');
GM_xmlhttpRequest({ method: "GET", url, onload: (res) => resolve(new DOMParser().parseFromString(res.responseText, "text/html")), onerror: (err) => reject(err) });
});
}
fetchUrlContent(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url, onload: (res) => resolve(res.responseText), 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.fetchApiPageContent(this.currentPage - 1);
prevPagePosts = this.parseMessagesFromDocument(prevPageDoc, true);
} catch (e) { console.error("JVC Enhancer: Impossible de charger la page N-1.", e); }
}
// Fusionner et dédoublonner par contenu HTML pour être plus précis
const allMessages = [...cachedPosts, ...pagePosts, ...prevPagePosts];
const uniqueMessages = Array.from(new Map(allMessages.map(item => [item.html, item])).values());
return uniqueMessages;
}
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 contentElement = msg.querySelector(sel.content);
if (contentElement) {
const clone = contentElement.cloneNode(true);
clone.querySelectorAll('blockquote.blockquote-jv').forEach(bq => bq.remove());
messages.push({
text: this.normalizeText(clone.textContent),
html: this.normalizeHtml(clone.innerHTML)
});
}
}
});
return messages;
}
addPostListener() {
const postButton = document.querySelector('.postMessage');
if (postButton) {
postButton.addEventListener('click', () => {
const messageTextarea = document.querySelector('#message_topic, .messageEditor__edit');
if (messageTextarea && this.topicId) {
// On sauvegarde le texte brut, la normalisation se fera à la lecture
this.saveUserPostToCache(messageTextarea.value);
}
}, true);
}
}
saveUserPostToCache(rawText) {
try {
const cache = JSON.parse(localStorage.getItem(this.CACHE_KEY_POSTS)) || {};
if (!cache[this.topicId]) cache[this.topicId] = [];
// On stocke le texte brut
cache[this.topicId].unshift(rawText);
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)) || {};
const posts = cache[this.topicId] || [];
// On transforme le texte brut en objets {text, html}
return posts.map(post => ({
text: this.normalizeText(post),
html: this.normalizeHtml(post)
}));
} 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 cleanQuoteHtml = this.normalizeHtml(quote.innerHTML);
if (this.currentUserMessages.some(myMsg => {
// Si mon message a du texte, on compare le texte
if (myMsg.text && this.normalizeText(quote.textContent).includes(myMsg.text)) return true;
// Si mon message n'a que du HTML (sticker), on compare le HTML
if (!myMsg.text && myMsg.html && cleanQuoteHtml.includes(myMsg.html)) return true;
return false;
})) {
return true;
}
}
return false;
}
normalizeText(text) { return text ? text.toLowerCase().replace(/\s+/g, ' ').trim() : ''; }
normalizeHtml(html) {
if (!html) return '';
// CORRECTION: La regex ne supprime que la date, pas le <p>
return html.replace(/Le .+? :<br>/i, '').replace(/\s+/g, ' ').trim();
}
getCurrentUsername() { return document.querySelector('.headerAccount__pseudo')?.textContent.trim() || null; }
injectStyles() {
const styles = `
.jvc-enhancer-mention { background-color: rgba(230, 195, 92, 0.07); border-left: 2px solid rgba(230, 195, 92, 0.75); }
.jvc-enhancer-op-mention { background-color: rgba(157, 105, 212, 0.07); border-left: 2px solid rgba(157, 105, 212, 0.75); }
.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); }
.jvc-enhancer-pemt { border-right-width: 2px; border-right-style: solid; }
.jvc-enhancer-ddb-reported { background-color: rgba(220, 53, 69, 0.07) !important; }
.jvc-enhancer-ddb-administered { background-color: rgba(25, 135, 84, 0.07) !important; }
.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(4px); -webkit-backdrop-filter: blur(4px);
border: 1px solid rgba(255, 255, 255, 0.1); box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
.badge-mention { background-color: rgba(230, 195, 92, 0.4); color: #212529; }
.badge-op-mention { background-color: rgba(157, 105, 212, 0.4); color: white; }
.badge-op, .badge-self, .badge-pemt { background-color: rgba(80, 80, 80, 0.5); 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), isMention = this.highlightRules[1].condition(author, el), 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);
});
this.processPEMT(messages);
}
processPEMT(messages) { 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.borderRightColor = color; if (!msg.querySelector('.jvc-enhancer-avatar-badge')) { this.createAvatarBadge(msg, 'PEMT', 'badge-pemt'); } }); pemtGroupIndex++; i += group.length - 1; } } }
createAvatarBadge(el, text, bClass, color = null) {
if (el.querySelector(`.jvc-enhancer-avatar-badge`)) return;
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}66`;
container.appendChild(badge);
}
async checkFirstMessagesReportStatus() {
if (this.currentPage !== 1) return;
const firstTwoMessages = Array.from(document.querySelectorAll('.bloc-message-forum')).slice(0, 2);
const ddbCache = JSON.parse(sessionStorage.getItem(this.CACHE_KEY_DDB)) || {};
for (const messageElement of firstTwoMessages) {
const reportIcon = messageElement.querySelector('.picto-msg-exclam');
const messageId = messageElement.dataset.id;
if (!reportIcon || !messageId) continue;
if (ddbCache[messageId]) { this.applyDdbHighlight(messageElement, ddbCache[messageId]); continue; }
const selectorUrl = reportIcon.dataset.selector;
if (!selectorUrl) continue;
const reportUrl = new URL(selectorUrl, "https://www.jeuxvideo.com").href;
try {
const responseText = await this.fetchUrlContent(reportUrl);
let status = 'clean';
if (responseText.includes("Ce contenu a déjà été signalé")) status = 'reported';
else if (responseText.includes("Ce contenu a déjà été modéré")) status = 'administered';
if (status !== 'clean') { ddbCache[messageId] = status; this.applyDdbHighlight(messageElement, status); }
} catch (error) { console.error(`JVC Enhancer: Erreur DDB pour msg ${messageId}`, error); }
}
sessionStorage.setItem(this.CACHE_KEY_DDB, JSON.stringify(ddbCache));
}
applyDdbHighlight(messageElement, status) {
messageElement.classList.remove('jvc-enhancer-mention', 'jvc-enhancer-op-mention');
if (status === 'reported') {
messageElement.classList.add('jvc-enhancer-ddb-reported');
} else if (status === 'administered') {
messageElement.classList.add('jvc-enhancer-ddb-administered');
}
}
}
new JVCTopicEnhancer();
})();