// ==UserScript==
// @name BetterFxP for Tampermonkey
// @namespace http://tampermonkey.net/
// @version 1.6
// @description מעולם לא היה קל יותר לגלוש ב-FxP.
// @author You
// @match https://www.fxp.co.il/*
// @supportURL https://discord.gg/AW7CeG7
// @require https://update.gf.qytechs.cn/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.gf.qytechs.cn/scripts/476628/1259426/fxp%20anti-delete%20PMs.user.js
// @license MIT
// ==/UserScript==
// @noframes
const checkbox = (label) => ({ label, default: false, type: 'checkbox' });
const text = (label, defaultValue = '', opt = {}) => ({ label, default: defaultValue, type: 'text', ...opt });
const cfg = new MonkeyConfig({
title: 'הגדרות FxPlus+',
menuCommand: true,
params: {
hideBigImages: checkbox("הסתר את הכתבות הגדולות מדך הבית"),
hideGames: checkbox("הסתר את אזור המשחקים מדף הבית"),
hideArticles: checkbox("הסתר כתבות מדף הבית"),
resizeSignatures: checkbox("חותך חתימות גדולות"),
hideAds: checkbox("הסתר מודעות"),
hideNagish: checkbox("הסתר את תפריט הנגישות"),
showFriends: checkbox("הצג חברים באשכולת"),
showAutoPinned: checkbox("הצג יותר משלושה אשכולות נעוצים"),
disableLiveTyping: checkbox("אל תודיע שאני מקליד"),
showDeletedPost: checkbox("הצג פוסט שנמחק"),
showLikeLimit: checkbox("הצג מגבלת לייקים"),
connectedStaff: checkbox("הצג צוות מחובר"),
showCounts: checkbox('מציג את מספר הפוסטים ואת כמות המשתמשים המחוברים'),
pms: checkbox("מציג הודעות פרטיות שנמחקו"),
showForumStats: checkbox('הצג סטטיסטיקות פורומים'),
weeklyChallenge: checkbox('מציג אתגרים שבועיים בתוך הפורום'),
audioChange: text(":קישור לקובץ שמע עבור התראה"), // https://www.tzevaadom.co.il/static/sounds/calm.wav
hideCategories: text(":רשימה של קטגוריות להסתרה"), // 4428, 13
smiles: text(':רשימה של קישורים לסמיילים', '', { long: 3 }), // https://yoursmiles.org/tsmile/heart/t4524.gif
nightMode: checkbox('הפעל את מצב הלילה אוטומטית'),
startTime: text('Start Time:', '17:00'),
endTime: text("End Time:", '23:50'),
color: text("צבע"),
font: text('פונט'),
size: text('גודל'),
// This is a temporary solution until I find a better library.
// TODO: md5 the pass
user1name: text("שם"),
user1pass: text("סיסמה"),
user2name: text("שם"),
user2pass: text("סיסמה"),
}
});
document.addEventListener('keydown', function (e) {
if (e.ctrlKey && e.key.toLowerCase() === 'y') {
e.preventDefault();
cfg.open("window", {
windowFeatures: { width: 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
- disable/enable on the same page and prevent reload
- 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 setCookie(name, value, minutes) {
const expires = new Date(Date.now() + minutes * 60 * 1000).toUTCString();
document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/`;
}
function getCookie(n) {
return decodeURIComponent(document.cookie.split('; ').find(c => c.startsWith(n + '='))?.split('=')[1] || '');
}
function onMatchIfLoggedIn(match, permissions, callback) {
rawWindow.LOGGEDIN && onMatch(match, permissions, callback);
}
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() {
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);
}
async function fetcher(url, opt = {}) {
const response = await fetch(url, opt);
return await response.text();
}
function Listener(callback) {
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url) {
this.method = method;
this.url = url;
originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function(body) {
this.body = body;
this.addEventListener("load", () => {
callback(this);
});
originalSend.apply(this, arguments);
};
}
// 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", "#slide { height:auto !important; } .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 = GM_addElement(iframeBody, 'a', {
href: publishedThreadUrl, target: '_blank'
});
if (!imgElement) creditLink.textContent = '+FxPlus';
else GM_addElement(creditLink, "img", { src: imgElement.src });
});
});
const smilieBox = document.querySelector('form[action*="signature"] .editor_smiliebox');
smilieBox.parentNode.insertBefore(creditAddon, smilieBox);
});
onMatchIfLoggedIn("*", "showFriends", async function () {
const storageKey = "refreshFriends";
if (getCookie(storageKey)) return;
const allFriendIds = [];
const regex = /<h4><a href="member\.php\?u=(\d+)"/g;
let page = 1;
while (true) {
const url = `https://www.fxp.co.il/profile.php?do=buddylist&pp=100&page=${page}`;
const html = await fetcher(url);
const matches = Array.from(html.matchAll(regex)).map(m => m[1]);
matches.forEach(id => allFriendIds.push(id));
if (matches.length < 100) break;
page++;
}
GM_setValue('friendIds', JSON.stringify(allFriendIds));
setCookie(storageKey, Date.now(), 15);
});
onMatchIfLoggedIn("show(post|thread)", "showFriends", function() {
const friendIds = JSON.parse(GM_getValue("friendIds", '[0]'));
const styleElement = GM_addStyle(`
${friendIds.map(id => '.username[href$="' + id + '"]::after').join(', ')} {
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");
let originalDescriptions = editor.config.smiley_descriptions,
originalImages = 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 = originalDescriptions;
editor.config.smiley_images = originalImages;
}
})
onMatchIfLoggedIn("show(post|thread)", "showLikeLimit", async function() {
let toRemove = [];
// For now, this is good. In the future, consider verifying the full name and not just if it includes certain text (to detect fake accounts).
async function checkLike(postid) {
const response = await fetcher("https://www.fxp.co.il/ajax.php", {
method: "POST",
headers: {
"content-type": "application/x-www-form-urlencoded",
},
body: `do=wholikepost&postid=${postid}&securitytoken=${rawWindow.SECURITYTOKEN}`,
});
return response.includes(rawWindow.my_user_name);
}
Listener(async e => {
if (e.method !== "POST" || e.url !== "ajax.php") {
return;
}
const postId = e.body.match(/\d+/);
if (!postId || await checkLike(postId)) return;
const element = document.getElementById(`${postId}_removelike`);
element.style.backgroundImage = 'url("https://em-content.zobj.net/source/google/387/broken-heart_1f494.png")';
toRemove.push(element);
});
return () => {
toRemove.forEach(el => el.style.backgroundImage = '');
toRemove = [];
}
})
// https://gf.qytechs.cn/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,
class: '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 team = document.querySelector(".teammen.flo");
team.dir = "auto"
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 () => {
team.dir = "";
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.textContent.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 = GM_addElement("div", {
style: `
position: fixed;
bottom: 10px;
right: 10px;
background: white;
color: white;
border: 1px solid #ccc;
padding: 10px 15px;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
border-radius: 8px;
z-index: 9999;
font-family: Arial, sans-serif;
font-size: 14px;
transition: opacity 1s`
});
container.innerHTML = `
<strong>סטטיסטיקה:</strong><br>
מחוברים: ${counts["#total_online"]?.toLocaleString() ?? 'N/A'}<br>
פוסטים: ${counts["#total_posts"]?.toLocaleString() ?? 'N/A'}
`;
setTimeout(() => {
container.style.opacity = "0";
container.addEventListener("transitionend", container.remove);
}, 5000);
});
//TODO: still needs improvement, but much better than before
onMatch("forumdisplay", "showForumStats", function() {
function getDupeSortedDictionary(arr) {
const counts = new Map();
for (const item of arr) counts.set(item, (counts.get(item) || 0) + 1);
const sortedArr = Array.from(counts, ([value, count]) => ({ value, count }));
sortedArr.sort((a, b) => {
return b.count === a.count ?
a.value.localeCompare(b.value) : b.count - a.count;
});
return sortedArr;
}
function openPopupWindow(title, content) {
let dialog = document.getElementById("detailedStats");
if (!dialog) {
dialog = GM_addElement("dialog", {
id: "detailedStats"
})
dialog.innerHTML = `${title}${content}`
dialog.addEventListener("click", (e) => {
if (e.target === dialog) dialog.close();
});
}
dialog.showModal();
}
function removePopupWindow(id) {
const dialog = document.getElementById(id);
if (dialog && dialog.open) {
dialog.close();
}
}
const total = document.querySelectorAll('#threads .threadtitle').length;
const container = document.createElement("div");
container.id = "forumStatsContainer";
const threadsList = document.querySelector(".threads_list_fxp");
threadsList?.parentNode?.insertBefore(container, threadsList.nextSibling);
const forumStats = GM_addElement(container, "div");
GM_addElement(forumStats, "i", { textContent: `נתונים סטטיסטיים של ${total} אשכולות:` });
const toArray = selector => Array.from(document.querySelectorAll(selector)).map(el => el.textContent);
function appendLine(dict, introText, noText, suffix) {
const line = GM_addElement(forumStats, "div");
if (dict.length > 1 && dict[0].count > 1) {
line.append(introText);
for (let i = 0; i < dict.length && dict[i].count === dict[0].count; i++) {
if (i > 0) line.append(" או ");
GM_addElement(line, "b", { textContent: dict[i].value });
}
line.append(" עם " + dict[0].count + suffix);
} else {
line.append(noText);
}
}
const publishersDict = getDupeSortedDictionary(toArray("#threads .threadinfo .username"));
const commentorsDict = getDupeSortedDictionary(toArray("#threads .threadlastpost .username"));
const prefixesDict = getDupeSortedDictionary(toArray("#threads .prefix").map(prefix => prefix.replace(/\||סקר: /g, '')));
appendLine(
publishersDict,
"המפרסם הדומיננטי ביותר הוא ",
"אין מפרסם דומיננטי במיוחד.",
" אשכולות."
);
appendLine(
commentorsDict,
"המגיב האחרון הדומיננטי ביותר הוא ",
"אין מגיב אחרון דומיננטי במיוחד.",
" תגובות אחרונות."
);
appendLine( //TODO: שנמצא ב-" + prefixesDict[0].count + " אשכולות.
prefixesDict,
"התיוג הנפוץ ביותר הוא ",
"אין תיוג נפוץ במיוחד.",
" אשכולות."
);
const parseNumber = text => parseInt(text.replace(/[^\d]/g, ""), 10) || 0;
let commentsCount = 0;
let viewsCount = 0;
document.querySelectorAll("#threads .threadstats").forEach(el => {
const [comments, views] = el.querySelectorAll("li");
commentsCount += parseNumber(comments.textContent);
viewsCount += parseNumber(views.textContent);
});
let viewsCommentsRatio = commentsCount > 0 ? Math.max(1, Math.round(viewsCount / commentsCount)) : "∞";
const ratioLine = document.createElement("div");
const b = document.createElement("b");
b.textContent = viewsCommentsRatio + " צפיות";
ratioLine.append("יחס הצפיות לתגובה הוא תגובה כל ", b, ".");
forumStats.appendChild(ratioLine);
const detailedStatsBtn = GM_addElement(forumStats, "div", { textContent: "+" });
detailedStatsBtn.addEventListener("click", () => {
const pContent = document.createElement("div");
const flexTableContainer = GM_addElement(pContent, "div", {
style: "display: flex; flexWrap: wrap;"
});
function helper(headerA, headerB, arr) {
const table = GM_addElement(flexTableContainer, "table");
const headerRow = GM_addElement(table, "tr")
GM_addElement(headerRow, "th", { textContent: headerA });
GM_addElement(headerRow, "th", { textContent: headerB });
arr.forEach(item => {
const tr = GM_addElement(table, "tr");
GM_addElement(tr, "td", { textContent: item.value });
GM_addElement(tr, "td", { textContent: item.count });
});
}
helper("מפרסם", "אשכולות", publishersDict);
helper("מגיב", "תגובות אחרונות", commentorsDict);
helper("תיוג", "אשכולות", prefixesDict);
const closeBtn = GM_addElement(pContent, "div", { textContent: "סגור" });
closeBtn.addEventListener("click", () => removePopupWindow("detailedStats"));
const forumTitle = document.querySelector('.lastnavbit > span').textContent;
openPopupWindow(
"סטטיסטיקות מפורטות לפורום " + forumTitle,
pContent.outerHTML
);
});
return () => {
document.querySelectorAll("#forumStatsContainer, dialog").forEach(e => e.remove());
};
});
onMatch("*", "nightMode", function () {
const toggleDarkMode = (isEnabled) => setCookie("bb_darkmode", isEnabled ? "1" : "0", 1440);
function timeInMinutes(timeString) {
if (!timeString) return 0;
const [hours, minutes] = timeString.split(':').map(Number);
return hours * 60 + minutes;
}
const darkModeThemeEl = document.querySelector("#darkmode_theme");
function exec() {
const now = new Date();
const minutesCurrent = now.getHours() * 60 + now.getMinutes();
const minutesStart = timeInMinutes(cfg.get("startTime"));
const minutesEnd = timeInMinutes(cfg.get("endTime"));
const rangeActive = minutesEnd < minutesStart
? (minutesCurrent >= minutesStart || minutesCurrent < minutesEnd)
: (minutesCurrent >= minutesStart && minutesCurrent < minutesEnd);
const nightModeActive = getCookie("bb_darkmode") == "1"
if (nightModeActive && !rangeActive) {
darkModeThemeEl?.classList?.remove('ofset');
document.body.classList.remove('darkmode');
document.querySelector('[href*="darkmode"]')?.remove();
toggleDarkMode(false);
} else if (!nightModeActive && rangeActive) {
darkModeThemeEl?.classList?.add('ofset');
document.body.classList.add('darkmode');
if (!document.querySelector('[href*="darkmode"]')) {
GM_addElement("link", {
rel: 'stylesheet',
href: '//static.fcdn.co.il/dyn/projects/css/desktop/darkmode.css'
});
}
toggleDarkMode(true);
}
};
exec();
const interval = setInterval(exec, 20 * 1000);
return () => clearInterval(interval);
});
onMatch("forumdisplay", "weeklyChallenge", async function() {
const getTodayDMY = () => new Date().toLocaleDateString('en-GB').split('/').join('-');
function parseDMY(dateStr) {
const [day, month, year] = dateStr.split('-').map(Number);
return new Date(year, month - 1, day);
}
function checkDateDifference(dateStr1, dateStr2) {
const date1 = parseDMY(dateStr1);
const date2 = parseDMY(dateStr2);
return Math.abs(date2 - date1) / 604800000; // 604800000 = week
}
const target = document.querySelector(".flo > .description_clean");
const container = GM_addElement(target, "div", {
style: "direction: rtl; text-align: right; max-width: 300px; padding: 10px;"
});
GM_addElement(container, "h3", {
textContent: "אשכול השבוע",
style: "margin-top: 0;"
});
GM_addElement(container, "div", {
id: "thread-week",
textContent: "טוען...",
style: "margin-bottom: 12px; color: #222;"
});
GM_addElement(container, "h3", {
textContent: "משתמש השבוע"
});
GM_addElement(container, "div", {
id: "member-week",
textContent: "טוען...",
style: "color: #222;"
});
const errorBox = GM_addElement(container, "div", {
id: "weekly-error",
style: "color: red; margin-top: 10px; display: none;"
});
const CACHE_KEY = "weeklyChallengeCache" + rawWindow.FORUM_ID_FXP;
const cachedData = getCookie(CACHE_KEY);
if (cachedData) {
try {
const { thread, member } = JSON.parse(cachedData);
document.getElementById("thread-week").textContent = thread;
document.getElementById("member-week").textContent = member;
return;
} catch (e) {}
}
const domParser = new DOMParser();
const stickies = Array.from(document.querySelectorAll('.stickies .threadinfo'))
.filter(node => /אשכול השבוע|משקיען השבוע|7 ימי ווינר|משקיען ואשכול/.test(node.textContent))
.map(node => node.parentElement);
if (stickies.length > 1 || stickies.length === 0) {
container.style.display = "none";
return; // maybe in the future, this will handle values greater than 1
}
const sticky = stickies.shift();
const element = sticky.querySelector('a.lastpostdate');
const url = element.href?.replace(/#post.*/, '');
if (!url) {
errorBox.style.display = "block";
errorBox.innerHTML = 'האשכול השבועי לא מכיל אף הכרזה, יש לפנות ל<a href="https://www.fxp.co.il/forumdisplay.php?f=18" target="_blank">צוות תמיכה</a>';
return;
}
const time = element.parentElement.textContent;
const date = time.split(" ").shift().replace(/אתמול|היום/, getTodayDMY());
if (checkDateDifference(getTodayDMY(), date) >= 2) {
errorBox.style.display = "block";
errorBox.innerHTML = 'האשכול השבועי לא עודכן זמן רב, יש לפנות ל<a href="https://www.fxp.co.il/forumdisplay.php?f=18" target="_blank">צוות תמיכה</a>';
return;
}
const response = await fetcher(url + "&pp=1");
const doc = domParser.parseFromString(response, "text/html");
const threads = doc.querySelectorAll(".postcontent a[href*='showthread.php']");
const members = doc.querySelectorAll(".postcontent a[href*='member.php']");
/*
TODO:
- Handle multiple thread links properly current implementation only processes the first link and does not cover all cases.
- Replace textContent below with real URL
*/
let thread = threads.length > 0 ? threads[0].textContent.trim().replace(/^"|"$/g, "") : "לא נמצא אשכול";
let member = members.length > 0 ? members[0].textContent.trim() : "לא נמצא משתמש";
document.getElementById("thread-week").textContent = thread;
document.getElementById("member-week").textContent = member;
setCookie(CACHE_KEY, JSON.stringify({ thread, member }), 5);
})
// TODO: check that every editor is compatible
// consider adding a check to validate the parameter
// currently does not support setting changes
// This code runs only once; executeCommand works better then
onMatch("show(post|thread)|newreply", "none", async function() {
const instances = await waitForObject("CKEDITOR.instances");
const editor = Object.values(instances)[0];
let content = editor.getData(); // document
if (!content.trim()) {
content = '\u200B';
}
['size', 'font', 'color'].forEach(style => {
const value = cfg.get(style);
if (!value) return;
content = `[${style}=${value}]${content}[/${style}]`;
})
editor.setData(content);
});
//temporally
onMatch("upload.php", 'none', function() {
const user1 = [cfg.get("user1name"), cfg.get("user1pass")]
if (user1.filter(Boolean).length) {
const b = GM_addElement(document.querySelector(".back_image"), "button", {
textContent: "התחבר ל-" + user1[0]
})
b.addEventListener("click", async function() {
await login(...user1)
});
}
const user2 = [cfg.get("user2name"), cfg.get("user2spass")]
if (user2.filter(Boolean).length) {
const b = GM_addElement(document.querySelector(".back_image"), "button", {
textContent: "התחבר ל-" + user2[0]
})
b.addEventListener("click", async function() {
await login(...user2)
});
}
});
async function login(vb_login_username, vb_login_password) {
const postData = {
method: "POST",
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
body: new URLSearchParams({
securitytoken: "guest",
vb_login_username,
vb_login_password,
cookieuser: 1,
do: "login"
})
};
try {
const data = await fetcher("https://www.fxp.co.il/login.php", postData);
if (data.includes("התחברת בהצלחה")) alert("✅ Logged in successfully");
else if (data.includes("במספר הפעמים המרבי")) alert("⚠️ Login restricted");
alert("❌ Login failed: Invalid credentials");
} catch (err) {
alert("Login request failed");
}
}