// ==UserScript==
// @name MZone Advanced: Table, Stats & Play-off / MZone Gelişmiş: Tablo, İstatistik & Play-off
// @name:tr MZone Gelişmiş: Tablo, İstatistik & Play-off
// @namespace http://tampermonkey.net/
// @version 2.30
// @description A powerful suite combining the Advanced League Table (live scores, FD), Player Stat Averages, and the new Play-off/Play-out Predictor. Now with Excel export, Shortlist Filtering and Transfer Tracker with charts.
// @description:tr Gelişmiş Lig Tablosu (canlı skorlar, FZ), Oyuncu İstatistik Ortalamaları ve yeni Play-Off/Play-Out Tahmincisi betiklerini tek bir güçlü araçta birleştirir. Şimdi Excel'e aktarma, Takip Listesi Filtreleme ve Grafikli Transfer Takipçisi özelliğiyle.
// @author alex66
// @match https://www.managerzone.com/?p=league*
// @match https://www.managerzone.com/?p=friendlyseries*
// @match https://www.managerzone.com/?p=private_cup*
// @match https://www.managerzone.com/?p=cup*
// @match https://www.managerzone.com/?p=players*
// @match https://www.managerzone.com/?p=player&pid=*
// @match https://www.managerzone.com/?p=match&sub=played*
// @match https://www.managerzone.com/?p=match&sub=result*
// @match https://www.managerzone.com/?p=match&sub=stats*
// @match https://www.managerzone.com/?p=shortlist*
// @match https://www.managerzone.com/?p=transfer*
// @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @run-at document-idle
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_openInTab
// @grant GM_info
// @grant GM_getResourceText
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM
// @require https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js
// @require https://code.highcharts.com/highcharts.js
// @resource NPROGRESS_CSS https://unpkg.com/[email protected]/nprogress.css
// ==/UserScript==
(async function() {
'use strict';
// DÜZELTME: Betiğin sayfanın jQuery'sine erişebilmesi için 'unsafeWindow' kullanılıyor.
// Bu, @grant direktifleri nedeniyle oluşan "sandbox" ortamını aşmak için gereklidir.
const $ = unsafeWindow.jQuery;
/****************************************************************************************
* *
* BÖLÜM 1: GELİŞMİŞ LİG TABLOSU BETİĞİ (ManagerZone Universal Advanced League Table) *
* *
****************************************************************************************/
function initializeLeagueTableScript() {
// =================================================================================
// BÖLÜM -1: ULUSLARARASILAŞTIRMA (i18n)
// =================================================================================
const i18n = {
translations: {
en: {
scriptName: 'ManagerZone Universal Advanced League Table',
leagueTableTitle: 'Table',
fixtureDifficultyHeader: 'FD',
fixtureDifficultyTitle: 'Fixture Difficulty (Higher value is advantageous, lower is disadvantageous)',
settingsTitle: 'Change settings',
fetchLiveScoresBtn: 'Fetch Live Scores',
processingBtn: 'Processing...',
tableUpdatedBtn: 'Table Updated!',
scriptUpdatedTitle: "'${scriptName}' has been updated!",
updateNotesIntro: "Here's what's new in version v${version}:",
updateModalCloseBtn: 'Got it, Close',
settingsModalTitle: 'Settings',
logoVisibilityLabel: 'Logo Visibility (Show team logos)',
saveBtn: 'Save',
roundPrefix: 'Round',
fzNotAvailable: 'Fixture analysis is not available for this page.',
fzTooltip: 'Remaining matches: ${remaining}\nOpponent rank sum: ${rankSum}',
fzNoMatchesLeft: 'No matches left',
locationHome: '(H)',
locationAway: '(A)',
/* YENİ EKLENEN ÇEVİRİLER */
home: 'Home',
away: 'Away',
overall: 'Overall',
updateNotes: {
'2.6': ["Fixed a critical bug where the script would fail on non-Turkish/English languages. The script now correctly parses fixture data regardless of the selected language and uses an English UI for all non-Turkish languages."]
}
},
tr: {
scriptName: 'ManagerZone Evrensel Gelişmiş Lig Tablosu',
leagueTableTitle: 'Puan Tablosu',
fixtureDifficultyHeader: 'FZ',
fixtureDifficultyTitle: 'Fikstür Zorluğu (Değer yüksekse avantajlı, düşükse dezavantajlı)',
settingsTitle: 'Ayarları değiştir',
fetchLiveScoresBtn: 'Canlı Maç Sonuçlarını Al',
processingBtn: 'İşleniyor...',
tableUpdatedBtn: 'Tablo Güncellendi!',
scriptUpdatedTitle: "'${scriptName}' Güncellendi!",
updateNotesIntro: "Yeni sürüm (v${version}) ile gelen yenilikler:",
updateModalCloseBtn: 'Anladım, Kapat',
settingsModalTitle: 'Ayarlar',
logoVisibilityLabel: 'Logo Görünürlüğü (Takım logoları gösterilsin)',
saveBtn: 'Kaydet',
roundPrefix: 'Tur',
fzNotAvailable: 'Fikstür analizi bu sayfa için mevcut değil.',
fzTooltip: 'Kalan maç: ${remaining}\nRakip sıra top: ${rankSum}',
fzNoMatchesLeft: 'Kalan maç yok',
locationHome: '(E)',
locationAway: '(D)',
/* YENİ EKLENEN ÇEVİRİLER */
home: 'İç Saha',
away: 'Dış Saha',
overall: 'Genel',
updateNotes: {
'2.6': ["Türkçe/İngilizce dışındaki dillerde betiğin çalışmamasına neden olan kritik hata düzeltildi. Betik artık seçilen dilden bağımsız olarak fikstür verisini doğru bir şekilde analiz ediyor ve Türkçe dışındaki tüm diller için İngilizce arayüz kullanıyor."]
}
}
},
detectLanguage: function() {
const langMeta = document.querySelector('meta[name="language"]');
if (langMeta && langMeta.getAttribute('content') === 'tr') {
return 'tr';
}
return 'en';
},
get: function(key, replacements = {}) {
let lang = this.currentLang || this.detectLanguage();
let text;
if (this.translations[lang] && this.translations[lang][key] !== undefined) {
text = this.translations[lang][key];
} else {
text = this.translations['en']?.[key];
}
if (text === undefined) {
console.warn(`i18n key not found: ${key}`);
return `[${key}]`;
}
if (key === 'updateNotes' && typeof text === 'object') {
return text;
}
for (const placeholder in replacements) {
text = text.replace(new RegExp(`\\$\\{${placeholder}\\}`, 'g'), replacements[placeholder]);
}
return text;
}
};
i18n.currentLang = i18n.detectLanguage();
// =================================================================================
// BÖLÜM 0: YAPILANDIRMA VE SABİTLER
// =================================================================================
const CONFIG = {
SELECTORS: {
LEAGUE_TABLE: 'table.nice_table',
TABLE_HEADER_ROW: 'table.nice_table thead tr',
TABLE_BODY: 'table.nice_table tbody',
TABLE_ROWS: 'table.nice_table tbody tr',
TEAM_LINK_IN_ROW: 'a[href*="&tid="]',
MY_TEAM_ROW: 'tr.highlight_row',
ONGOING_MATCH_LINK: 'table.hitlist a[href*="mid="], .team-matches-vs a[href*="mid="]',
LEAGUE_TABLE_HEADER: 'h2.subheader',
SCHEDULE_TAB_LINK: '#league_tab_schedule',
UPDATE_BUTTON: '.mz-fetch-button',
SETTINGS_BUTTON: '.mz-settings-button',
CONTENT_DIV: '#contentDiv',
SETTINGS_MODAL: '#mz-settings-modal',
SETTINGS_MODAL_CLOSE: '.mz-modal-close',
SETTINGS_MODAL_SAVE: '#mz-settings-save',
LOGO_VISIBILITY_CHECKBOX: '#mz-logo-visibility-checkbox',
UPDATE_MODAL: '#mz-update-modal',
UPDATE_MODAL_CLOSE: '#mz-update-close-button',
},
CLASSES: {
NEXT_OPPONENT: 'mz-next-opponent',
UPDATED_CELL: 'updated-cell',
INDICATORS_WRAPPER: 'mz-indicators-wrapper',
TOOLTIP: 'mz-custom-tooltip',
BUTTON_DISABLED: 'disabled',
BUTTON_DONE: 'done',
FZ_HEADER: 'mz-fz-header',
TEAM_LOGO: 'mz-team-logo',
SETTINGS_ACTIVE: 'mz-settings-active',
},
COLUMNS: {
POSITION: 0,
TEAM_NAME: 1,
PLAYED: 2,
WINS: 3,
DRAWS: 4,
LOSSES: 5,
GOALS_FOR: 6,
GOALS_AGAINST: 7,
GOAL_DIFFERENCE: 8,
POINTS: 9,
FIXTURE_DIFFICULTY: 10
},
API: {
MATCH_INFO: 'https://www.managerzone.com/xml/match_info.php?sport_id=1&match_id=',
}
};
const SCRIPT_INFO = GM_info.script;
const LAST_SEEN_VERSION_KEY = 'mz_advanced_table_last_version';
const UPDATE_NOTES = i18n.get('updateNotes');
function injectStyles() {
GM_addStyle(GM_getResourceText('NPROGRESS_CSS'));
GM_addStyle(`
.mz-team-cell { display: flex; align-items: center; }
.mz-team-logo { height: 2.0em; width: auto; margin-right: 6px; vertical-align: middle; flex-shrink: 0; }
.mz-indicators-wrapper { margin-left: auto; margin-right: 15px; display: flex; align-items: center; white-space: nowrap; padding-left: 10px; }
.match-indicator { display: inline-block; width: 13.5px; height: 12.5px; border-radius: 50%; margin-left: 8px; vertical-align: middle; border: 1px solid rgba(0,0,0,0.6); cursor: pointer; flex-shrink: 0; }
.match-win { background-color: #28a745; }
.match-loss { background-color: #dc3545; }
.match-draw { background-color: #ffc107; }
.match-future { font-size: 11px; color: #000; margin-left: 5px; font-style: italic; font-weight: bold; }
.mz-next-opponent { background-color: #fff3cd !important; font-weight: bold; }
.mz-next-opponent:hover { background-color: #ffeeba !important; }
.mz-next-opponent a { color: #856404 !important; }
.mz-custom-tooltip { position: absolute; display: none; padding: 8px 12px; color: #fff; border-radius: 6px; font-size: 14px; font-weight: bold; z-index: 9999; pointer-events: none; box-shadow: 0 4px 8px rgba(0,0,0,0.3); text-shadow: 1px 1px 2px rgba(0,0,0,0.5); line-height: 1; white-space: nowrap; }
.mz-custom-tooltip::before { content: ''; position: absolute; top: 50%; right: 100%; margin-top: -6px; border-width: 6px; border-style: solid; }
.tooltip-win { background-color: #28a745; border: 1px solid #1e7e34; }
.tooltip-win::before { border-color: transparent #1e7e34 transparent transparent; }
.tooltip-loss { background-color: #dc3545; border: 1px solid #b21f2d; }
.tooltip-loss::before { border-color: transparent #b21f2d transparent transparent; }
.tooltip-draw { background-color: #ffc107; border: 1px solid #d39e00; color: #212529; text-shadow: none; }
.tooltip-draw::before { border-color: transparent #d39e00 transparent transparent; }
.mz-fetch-button { margin-left: 10px; padding: 5px 10px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; transition: background-color 0.3s; }
.mz-fetch-button:hover:not(.disabled) { background-color: #45a049; }
.mz-fetch-button.disabled { background-color: #ccc; cursor: not-allowed; }
.mz-fetch-button.done { background-color: #007bff; }
.${CONFIG.CLASSES.FZ_HEADER}, table.nice_table td:nth-child(${CONFIG.COLUMNS.FIXTURE_DIFFICULTY + 1}) { text-align: center; font-weight: bold; cursor: help; }
.mz-settings-button { margin-left: 8px; cursor: pointer; color: #999; font-size: 1.1em; transition: color 0.3s; vertical-align: middle;}
.mz-settings-button:hover { color: #333; }
.mz-settings-button.mz-settings-active { color: #4CAF50; }
.mz-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6); z-index: 10000; display: none; justify-content: center; align-items: center; }
.mz-modal-content { background: #fefefe; padding: 20px; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); min-width: 300px; display: flex; flex-direction: column; max-width: 500px; }
.mz-modal-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #ddd; padding-bottom: 10px; margin-bottom: 15px; }
.mz-modal-header h3 { margin: 0; font-size: 18px; }
.mz-modal-close { font-size: 24px; font-weight: bold; cursor: pointer; color: #888; }
.mz-modal-close:hover { color: #000; }
.mz-modal-body { display: flex; flex-direction: column; gap: 15px; }
.mz-modal-body label { display: flex; align-items: center; font-size: 16px; cursor: pointer; }
.mz-modal-body input[type="checkbox"] { width: 18px; height: 18px; margin-right: 10px; }
.mz-modal-footer { border-top: 1px solid #ddd; padding-top: 15px; margin-top: 20px; text-align: right; }
#mz-settings-save { padding: 8px 16px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
#mz-settings-save:hover { background-color: #45a049; }
#mz-update-modal .mz-modal-body ul { list-style-type: none; padding-left: 0; margin: 10px 0; }
#mz-update-modal .mz-modal-body li { margin-bottom: 10px; padding-left: 20px; position: relative; }
#mz-update-modal .mz-modal-body li::before { content: '✓'; color: #4CAF50; position: absolute; left: 0; font-weight: bold; }
#mz-update-modal .mz-modal-footer { text-align: center; }
#mz-update-close-button { padding: 10px 25px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; }
#mz-update-close-button:hover { background-color: #0069d9; }
@media (max-width: 768px) {
.mz-team-logo { height: 1.6em !important; width: auto !important; max-width: 20px !important; }
/* === MOBİL GÖRÜNÜM DÜZELTMESİ BURADA === */
.mz-indicators-wrapper {
flex-wrap: nowrap; /* İkonların ve yazıların alt satıra kaymasını engeller */
margin-left: auto; /* İkon grubunu hücrenin sağına yaslar */
margin-right: 5px;
justify-content: flex-end;
}
/* === DÜZELTME SONU === */
.mz-team-cell a { white-space: normal; }
}
.mz-table-filters .mzbtn span {
text-shadow: none !important;
}
`);
}
function request(options) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({ ...options,
onload: resolve,
onerror: reject,
ontimeout: reject
});
});
}
let myTeamInfo = {
id: null,
name: null
};
let originalTableBody = null;
const liveMatchResults = new Map();
const tooltip = document.createElement('div');
document.body.appendChild(tooltip);
let fullScheduleCache = null;
let isLogoDisplayEnabled = true;
let allPlayedMatchesCache = null; // YENİ EKLENEN DEĞİŞKEN
function findOngoingMatches() {
const ongoingMatches = [];
document.querySelectorAll(CONFIG.SELECTORS.ONGOING_MATCH_LINK).forEach(link => {
const scoreText = link.textContent.trim();
if (!/^\d+\s*-\s*\d+$/.test(scoreText) && !scoreText.includes('X - X')) {
const mid = new URLSearchParams(link.href).get('mid');
if (mid) ongoingMatches.push({
mid,
linkElement: link
});
}
});
return ongoingMatches;
}
function findMainLeagueTableHeader() {
const allHeaders = document.querySelectorAll(CONFIG.SELECTORS.LEAGUE_TABLE_HEADER);
for (const header of allHeaders) {
const nextElement = header.nextElementSibling;
if (nextElement && (nextElement.matches(CONFIG.SELECTORS.LEAGUE_TABLE) || nextElement.querySelector(CONFIG.SELECTORS.LEAGUE_TABLE))) {
return header;
}
}
return null;
}
function isMainLeagueTableVisible() {
const tables = document.querySelectorAll('#contentDiv table.nice_table');
for (const table of tables) {
const header = table.querySelector('thead');
const body = table.querySelector('tbody');
if (header && body && body.rows.length > 2 && header.rows[0].cells.length >= 8) {
return true;
}
}
return false;
}
function setupLiveUpdateFeature() {
const ongoingMatches = findOngoingMatches();
if (ongoingMatches.length === 0) return;
const titleHeader = findMainLeagueTableHeader();
if (!titleHeader || document.querySelector(CONFIG.SELECTORS.UPDATE_BUTTON)) return;
const button = document.createElement('button');
button.className = 'mz-fetch-button';
button.textContent = i18n.get('fetchLiveScoresBtn');
button.addEventListener('click', () => handleLiveUpdateClick(button, ongoingMatches));
titleHeader.appendChild(button);
}
function createUpdateModal(version, notes) {
if (document.querySelector(CONFIG.SELECTORS.UPDATE_MODAL)) return;
const modalOverlay = document.createElement('div');
modalOverlay.id = 'mz-update-modal';
modalOverlay.className = 'mz-modal-overlay';
const notesHtml = notes.map(note => `<li>${note}</li>`).join('');
const scriptName = i18n.get('scriptName');
modalOverlay.innerHTML = `
<div class="mz-modal-content">
<div class="mz-modal-header">
<h3>${i18n.get('scriptUpdatedTitle', { scriptName })}</h3>
</div>
<div class="mz-modal-body">
<p>${i18n.get('updateNotesIntro', { version })}</p>
<ul>${notesHtml}</ul>
</div>
<div class="mz-modal-footer">
<button id="mz-update-close-button">${i18n.get('updateModalCloseBtn')}</button>
</div>
</div>`;
document.body.appendChild(modalOverlay);
modalOverlay.querySelector(CONFIG.SELECTORS.UPDATE_MODAL_CLOSE).addEventListener('click', async () => {
modalOverlay.style.display = 'none';
await GM_setValue(LAST_SEEN_VERSION_KEY, SCRIPT_INFO.version);
});
}
function createSettingsModal() {
if (document.querySelector(CONFIG.SELECTORS.SETTINGS_MODAL)) return;
const modalOverlay = document.createElement('div');
modalOverlay.id = 'mz-settings-modal';
modalOverlay.className = 'mz-modal-overlay';
modalOverlay.innerHTML = `
<div class="mz-modal-content">
<div class="mz-modal-header">
<h3>${i18n.get('settingsModalTitle')}</h3>
<span class="mz-modal-close">×</span>
</div>
<div class="mz-modal-body">
<label>
<input type="checkbox" id="mz-logo-visibility-checkbox" />
${i18n.get('logoVisibilityLabel')}
</label>
</div>
<div class="mz-modal-footer">
<button id="mz-settings-save">${i18n.get('saveBtn')}</button>
</div>
</div>`;
document.body.appendChild(modalOverlay);
modalOverlay.querySelector(CONFIG.SELECTORS.SETTINGS_MODAL_CLOSE).addEventListener('click', closeSettingsModal);
modalOverlay.querySelector(CONFIG.SELECTORS.SETTINGS_MODAL_SAVE).addEventListener('click', saveSettingsAndClose);
modalOverlay.addEventListener('click', (e) => {
if (e.target === modalOverlay) {
closeSettingsModal();
}
});
}
function openSettingsModal() {
const modal = document.querySelector(CONFIG.SELECTORS.SETTINGS_MODAL);
const checkbox = document.querySelector(CONFIG.SELECTORS.LOGO_VISIBILITY_CHECKBOX);
if (!modal || !checkbox) return;
checkbox.checked = isLogoDisplayEnabled;
modal.style.display = 'flex';
}
function closeSettingsModal() {
const modal = document.querySelector(CONFIG.SELECTORS.SETTINGS_MODAL);
if (modal) modal.style.display = 'none';
}
function saveSettingsAndClose() {
const checkbox = document.querySelector(CONFIG.SELECTORS.LOGO_VISIBILITY_CHECKBOX);
if (!checkbox) return;
isLogoDisplayEnabled = checkbox.checked;
GM_setValue('showTeamLogos', isLogoDisplayEnabled);
updateSettingsIconState();
toggleLogoVisibility();
closeSettingsModal();
}
function setupSettingsMenu() {
const titleHeader = findMainLeagueTableHeader();
if (!titleHeader || document.querySelector(CONFIG.SELECTORS.SETTINGS_BUTTON)) return;
const settingsIcon = document.createElement('span');
settingsIcon.className = CONFIG.CLASSES.SETTINGS_BUTTON;
settingsIcon.textContent = '⚙️';
settingsIcon.addEventListener('click', openSettingsModal);
titleHeader.appendChild(settingsIcon);
updateSettingsIconState();
}
function updateSettingsIconState() {
const settingsIcon = document.querySelector(CONFIG.SELECTORS.SETTINGS_BUTTON);
if (!settingsIcon) return;
settingsIcon.classList.toggle(CONFIG.CLASSES.SETTINGS_ACTIVE, isLogoDisplayEnabled);
settingsIcon.title = i18n.get('settingsTitle');
}
async function handleLiveUpdateClick(button, matches) {
button.textContent = i18n.get('processingBtn');
button.classList.add(CONFIG.CLASSES.BUTTON_DISABLED);
button.disabled = true;
NProgress.start();
const currentTbody = document.querySelector(CONFIG.SELECTORS.TABLE_BODY);
if (currentTbody && originalTableBody) {
currentTbody.parentNode.replaceChild(originalTableBody.cloneNode(true), currentTbody);
addFixtureDifficultyColumn();
toggleLogoVisibility();
}
liveMatchResults.clear();
for (let i = 0; i < matches.length; i++) {
NProgress.set((i / matches.length) * 0.5);
try {
const response = await request({
method: 'GET',
url: CONFIG.API.MATCH_INFO + matches[i].mid
});
const xmlDoc = new DOMParser().parseFromString(response.responseText, 'application/xml');
const homeNode = xmlDoc.querySelector('Team[field="home"]');
const awayNode = xmlDoc.querySelector('Team[field="away"]');
if (homeNode && awayNode) {
const matchData = {
mid: matches[i].mid,
homeTid: homeNode.getAttribute('id'),
awayTid: awayNode.getAttribute('id'),
homeGoals: parseInt(homeNode.getAttribute('goals'), 10) || 0,
awayGoals: parseInt(awayNode.getAttribute('goals'), 10) || 0
};
liveMatchResults.set(matchData.mid, matchData);
matches[i].linkElement.textContent = `${matchData.homeGoals} - ${matchData.awayGoals}`;
}
} catch (error) {
console.error(`Maç ID ${matches[i].mid} için veri çekilemedi:`, error);
}
}
NProgress.set(0.6);
liveMatchResults.forEach(data => {
const {
homeResult,
awayResult
} = calculateMatchResult(data);
updateTeamRow(data.homeTid, homeResult);
updateTeamRow(data.awayTid, awayResult);
});
NProgress.set(0.8);
sortTableByPoints();
await calculateAndDisplayFixtureDifficulty(liveMatchResults);
if (myTeamInfo.id) {
NProgress.set(0.9);
await performFullOpponentAnalysis();
}
NProgress.done();
button.textContent = i18n.get('tableUpdatedBtn');
button.classList.add(CONFIG.CLASSES.BUTTON_DONE);
}
function calculateMatchResult(data) {
if (data.homeGoals > data.awayGoals) return {
homeResult: {
p: 3,
w: 1,
d: 0,
l: 0,
gf: data.homeGoals,
ga: data.awayGoals
},
awayResult: {
p: 0,
w: 0,
d: 0,
l: 1,
gf: data.awayGoals,
ga: data.homeGoals
}
};
if (data.homeGoals < data.awayGoals) return {
homeResult: {
p: 0,
w: 0,
d: 0,
l: 1,
gf: data.homeGoals,
ga: data.awayGoals
},
awayResult: {
p: 3,
w: 1,
d: 0,
l: 0,
gf: data.awayGoals,
ga: data.homeGoals
}
};
return {
homeResult: {
p: 1,
w: 0,
d: 1,
l: 0,
gf: data.homeGoals,
ga: data.awayGoals
},
awayResult: {
p: 1,
w: 0,
d: 1,
l: 0,
gf: data.awayGoals,
ga: data.homeGoals
}
};
}
function updateTeamRow(tid, result) {
const teamRow = document.querySelector(`.nice_table a[href*="&tid=${tid}"]`)?.closest('tr');
if (!teamRow) return;
const cells = teamRow.cells;
const C = CONFIG.COLUMNS;
const parseCellInt = (index) => parseInt(cells[index]?.textContent.trim(), 10) || 0;
const updateCell = (index, value) => {
if (cells[index]) {
cells[index].textContent = value;
cells[index].classList.add(CONFIG.CLASSES.UPDATED_CELL);
setTimeout(() => cells[index].classList.remove(CONFIG.CLASSES.UPDATED_CELL), 2000);
}
};
updateCell(C.PLAYED, parseCellInt(C.PLAYED) + 1);
updateCell(C.WINS, parseCellInt(C.WINS) + result.w);
updateCell(C.DRAWS, parseCellInt(C.DRAWS) + result.d);
updateCell(C.LOSSES, parseCellInt(C.LOSSES) + result.l);
const newGF = parseCellInt(C.GOALS_FOR) + result.gf;
const newGA = parseCellInt(C.GOALS_AGAINST) + result.ga;
updateCell(C.GOALS_FOR, newGF);
updateCell(C.GOALS_AGAINST, newGA);
updateCell(C.GOAL_DIFFERENCE, newGF - newGA);
updateCell(C.POINTS, parseCellInt(C.POINTS) + result.p);
}
function applyPromotionRelegationStyles() {
const tableRows = document.querySelectorAll(CONFIG.SELECTORS.TABLE_ROWS);
if (!tableRows || tableRows.length < 1) return;
// Önce mevcut tüm stilleri temizle ki yanlış yerde kalmasınlar.
tableRows.forEach(row => {
row.style.borderBottom = '';
});
// Gerekli satırlar varsa stilleri uygula
// 7. sıraya kesikli kırmızı çizgi (Play-out)
if (tableRows.length >= 7) {
const playOutRow = tableRows[6]; // 7. sıra (index 6)
playOutRow.style.borderBottom = '2px dashed #D60000';
}
// 9. sıraya düz kırmızı çizgi (Düşme)
if (tableRows.length >= 9) {
const relegationRow = tableRows[8]; // 9. sıra (index 8)
relegationRow.style.borderBottom = '2px solid red';
}
}
function sortTableByPoints() {
const tbody = document.querySelector(CONFIG.SELECTORS.TABLE_BODY);
if (!tbody) return;
const rows = Array.from(tbody.rows);
const C = CONFIG.COLUMNS;
rows.sort((a, b) => {
const val = (r, i) => parseInt(r.cells[i]?.textContent.trim(), 10) || 0;
return val(b, C.POINTS) - val(a, C.POINTS) || val(b, C.GOAL_DIFFERENCE) - val(a, C.GOAL_DIFFERENCE) || val(b, C.GOALS_FOR) - val(a, C.GOALS_FOR);
});
rows.forEach((row, index) => {
row.cells[C.POSITION].textContent = index + 1;
tbody.appendChild(row);
});
// YENİ EKLENEN SATIR: Sıralama sonrası çizgileri yeniden uygula.
applyPromotionRelegationStyles();
}
function toggleLogoVisibility() {
if (isLogoDisplayEnabled) {
addTeamLogos();
} else {
removeTeamLogos();
}
}
function addTeamLogos() {
if (!isLogoDisplayEnabled) return;
document.querySelectorAll(CONFIG.SELECTORS.TABLE_ROWS).forEach(row => {
const teamLink = row.querySelector(CONFIG.SELECTORS.TEAM_LINK_IN_ROW);
if (teamLink && teamLink.parentNode && !row.querySelector(`.${CONFIG.CLASSES.TEAM_LOGO}`)) {
const tid = new URLSearchParams(teamLink.href).get('tid');
if (tid) {
const logo = document.createElement('img');
logo.src = `/dynimg/badge.php?team_id=${tid}&sport=soccer`;
logo.className = CONFIG.CLASSES.TEAM_LOGO;
logo.alt = 'Logo';
teamLink.parentNode.insertBefore(logo, teamLink);
}
}
});
}
function removeTeamLogos() {
document.querySelectorAll(`.${CONFIG.CLASSES.TEAM_LOGO}`).forEach(logo => logo.remove());
}
async function performFullOpponentAnalysis() {
try {
if (!myTeamInfo.name || !fullScheduleCache) return;
const fixtureData = parseMyTeamFixture(fullScheduleCache.htmlText);
applyAllVisuals(fixtureData);
} catch (error) {
console.error("Opponent analysis failed:", error);
}
}
function applyAllVisuals({
fixture,
nextOpponentName
}) {
if (!fixture) return;
const tableBody = document.querySelector(CONFIG.SELECTORS.TABLE_BODY);
if (!tableBody) return;
document.querySelectorAll(`.${CONFIG.CLASSES.INDICATORS_WRAPPER}`).forEach(el => el.remove());
document.querySelectorAll(`.${CONFIG.CLASSES.NEXT_OPPONENT}`).forEach(el => el.classList.remove(CONFIG.CLASSES.NEXT_OPPONENT));
tableBody.querySelectorAll("tr").forEach(row => {
const teamLink = row.querySelector(CONFIG.SELECTORS.TEAM_LINK_IN_ROW);
if (!teamLink || row.classList.contains('highlight_row')) return;
const teamCell = teamLink.parentNode;
teamCell.classList.add('mz-team-cell');
const teamName = teamLink.textContent.trim();
const result = fixture[teamName];
if (nextOpponentName === teamName) row.classList.add(CONFIG.CLASSES.NEXT_OPPONENT);
if (result) {
const wrapper = document.createElement('span');
wrapper.className = CONFIG.CLASSES.INDICATORS_WRAPPER;
if (result.played && result.played.length > 0) {
result.played.forEach(playedMatch => {
const icon = document.createElement('span');
icon.className = `match-indicator match-${playedMatch.status}`;
icon.dataset.tooltipText = `${playedMatch.location} ${playedMatch.score}`;
icon.dataset.status = playedMatch.status;
icon.addEventListener('click', () => GM_openInTab(playedMatch.matchURL, true));
addTooltipEvents(icon);
wrapper.appendChild(icon);
});
}
if (result.futureRounds.length > 0) {
const text = document.createElement('span');
text.className = 'match-future';
text.textContent = `(${result.futureRounds.join(', ')})`;
wrapper.appendChild(text);
}
if (wrapper.hasChildNodes()) teamCell.appendChild(wrapper);
}
});
}
function addTooltipEvents(element) {
element.addEventListener('mouseenter', (e) => {
const target = e.currentTarget;
const wrapperElement = target.closest(`.${CONFIG.CLASSES.INDICATORS_WRAPPER}`);
const positioningElement = wrapperElement || target;
const rect = positioningElement.getBoundingClientRect();
tooltip.className = `${CONFIG.CLASSES.TOOLTIP} tooltip-${target.dataset.status}`;
tooltip.textContent = target.dataset.tooltipText;
tooltip.style.display = 'block';
tooltip.style.top = `${rect.top + window.scrollY + rect.height / 2 - tooltip.offsetHeight / 2}px`;
tooltip.style.left = `${rect.right + window.scrollX + 10}px`;
});
element.addEventListener('mouseleave', () => {
tooltip.style.display = 'none';
});
}
function addFixtureDifficultyColumn() {
const headerRow = document.querySelector(CONFIG.SELECTORS.TABLE_HEADER_ROW);
if (!headerRow || headerRow.querySelector(`.${CONFIG.CLASSES.FZ_HEADER}`)) return;
const newHeader = document.createElement('th');
newHeader.textContent = i18n.get('fixtureDifficultyHeader');
newHeader.title = i18n.get('fixtureDifficultyTitle');
newHeader.classList.add(CONFIG.CLASSES.FZ_HEADER);
headerRow.insertBefore(newHeader, headerRow.children[CONFIG.COLUMNS.POINTS + 1]);
document.querySelectorAll(CONFIG.SELECTORS.TABLE_ROWS).forEach(row => {
const newCell = row.insertCell(CONFIG.COLUMNS.FIXTURE_DIFFICULTY);
newCell.textContent = '...';
});
}
async function fetchAndCacheSchedule() {
if (fullScheduleCache) return fullScheduleCache;
const scheduleTabLink = document.querySelector(CONFIG.SELECTORS.SCHEDULE_TAB_LINK);
if (!scheduleTabLink) return null;
try {
const response = await request({
method: "GET",
url: scheduleTabLink.href
});
fullScheduleCache = {
htmlText: response.responseText
};
return fullScheduleCache;
} catch (error) {
console.error("Could not fetch schedule page:", error);
return null;
}
}
function parseMyTeamFixture(htmlText) {
const doc = new DOMParser().parseFromString(htmlText, 'text/html');
const fixture = {};
let nextMatch = {
round: Infinity,
opponentName: null
};
const roundRegex = /\d+/;
doc.querySelectorAll(CONFIG.SELECTORS.LEAGUE_TABLE_HEADER).forEach(header => {
const roundMatch = header.textContent.match(roundRegex);
if (!roundMatch) return;
const round = parseInt(roundMatch[0], 10);
const table = header.nextElementSibling?.querySelector('table');
if (!table) return;
table.querySelectorAll('tr').forEach(row => {
const [homeCell, scoreCell, awayCell] = row.cells;
if (!homeCell || !scoreCell || !awayCell) return;
const homeTeam = homeCell.textContent.trim();
const awayTeam = awayCell.textContent.trim();
if (homeTeam === myTeamInfo.name || awayTeam === myTeamInfo.name) {
const opponentName = homeTeam === myTeamInfo.name ? awayTeam : homeTeam;
if (!fixture[opponentName]) fixture[opponentName] = {
played: [],
futureRounds: []
};
const scoreLink = scoreCell.querySelector('a');
const mid = scoreLink ? new URLSearchParams(scoreLink.href).get('mid') : null;
const location = homeTeam === myTeamInfo.name ? i18n.get('locationHome') : i18n.get('locationAway');
const currentRoundText = `${i18n.get('roundPrefix')} ${round}`;
if (mid && liveMatchResults.has(mid)) {
const d = liveMatchResults.get(mid);
const myScore = d.homeTid === myTeamInfo.id ? d.homeGoals : d.awayGoals;
const oppScore = d.homeTid === myTeamInfo.id ? d.awayGoals : d.homeGoals;
fixture[opponentName].played.push({
score: `${myScore} - ${oppScore}`,
matchURL: scoreLink.href,
location: location,
status: myScore > oppScore ? 'win' : (myScore < oppScore ? 'loss' : 'draw'),
});
} else if (scoreLink && /^\d+\s*-\s*\d+$/.test(scoreLink.textContent.trim())) {
const scores = scoreLink.textContent.trim().split('-').map(s => parseInt(s.trim()));
const myScore = homeTeam === myTeamInfo.name ? scores[0] : scores[1];
const oppScore = homeTeam === myTeamInfo.name ? scores[1] : scores[0];
fixture[opponentName].played.push({
score: `${myScore} - ${oppScore}`,
matchURL: scoreLink.href,
location: location,
status: myScore > oppScore ? 'win' : (myScore < oppScore ? 'loss' : 'draw'),
});
} else {
fixture[opponentName].futureRounds.push(currentRoundText);
if (round < nextMatch.round) nextMatch = {
round,
opponentName
};
}
}
});
});
return {
fixture,
nextOpponentName: nextMatch.opponentName
};
}
// DEĞİŞİKLİK BAŞLANGICI: Evrenselleştirilmiş Fonksiyon
function parseScheduleForAllTeams(htmlText, newlyFinishedMatches = new Map()) {
const doc = new DOMParser().parseFromString(htmlText, 'text/html');
const allFixtures = {};
document.querySelectorAll(CONFIG.SELECTORS.TABLE_ROWS).forEach(row => {
const teamLink = row.querySelector(CONFIG.SELECTORS.TEAM_LINK_IN_ROW);
if (teamLink) allFixtures[teamLink.textContent.trim()] = {
futureOpponents: []
};
});
const matchRows = doc.querySelectorAll('table.hitlist tbody tr, .matches_container table tbody tr');
matchRows.forEach(row => {
if (row.cells.length < 3) return;
const scoreCell = row.cells[1];
const scoreText = scoreCell.textContent.trim();
const scoreLink = scoreCell.querySelector('a[href*="mid="]');
const mid = scoreLink ? new URLSearchParams(scoreLink.href).get('mid') : null;
// ESKİ HALİ (Kırılgan): if ((!/^\d+\s*-\s*\d+$/.test(scoreText) || scoreText.includes('vs')) && !newlyFinishedMatches.has(mid))
// YENİ HALİ (Evrensel): Oynanmamış bir maçı tespit etmek için sadece skor formatını kontrol etmek yeterlidir.
// Bu, 'vs', 'gegen', 'X-X' veya diğer dil varyasyonlarını otomatik olarak yakalar.
if (!/^\d+\s*-\s*\d+$/.test(scoreText) && !newlyFinishedMatches.has(mid)) {
const homeTeam = row.cells[0].textContent.trim();
const awayTeam = row.cells[2].textContent.trim();
if (allFixtures[homeTeam] && allFixtures[awayTeam]) {
allFixtures[homeTeam].futureOpponents.push(awayTeam);
allFixtures[awayTeam].futureOpponents.push(homeTeam);
}
}
});
return allFixtures;
}
// DEĞİŞİKLİK SONU
async function calculateAndDisplayFixtureDifficulty(newlyFinishedMatches = new Map()) {
const scheduleData = await fetchAndCacheSchedule();
if (!scheduleData) {
document.querySelectorAll(`td:nth-child(${CONFIG.COLUMNS.FIXTURE_DIFFICULTY + 1})`).forEach(cell => {
if (cell) {
cell.textContent = '-';
cell.title = i18n.get('fzNotAvailable');
}
});
return;
}
const allFixtures = parseScheduleForAllTeams(scheduleData.htmlText, newlyFinishedMatches);
const teamRankings = new Map();
document.querySelectorAll(CONFIG.SELECTORS.TABLE_ROWS).forEach(row => {
const teamLink = row.querySelector(CONFIG.SELECTORS.TEAM_LINK_IN_ROW);
const rank = parseInt(row.cells[CONFIG.COLUMNS.POSITION].textContent, 10);
if (teamLink && !isNaN(rank)) teamRankings.set(teamLink.textContent.trim(), rank);
});
document.querySelectorAll(CONFIG.SELECTORS.TABLE_ROWS).forEach(row => {
const teamLink = row.querySelector(CONFIG.SELECTORS.TEAM_LINK_IN_ROW);
if (!teamLink) return;
const teamName = teamLink.textContent.trim();
const teamFixture = allFixtures[teamName];
const fzCell = row.cells[CONFIG.COLUMNS.FIXTURE_DIFFICULTY];
if (fzCell && teamFixture && teamFixture.futureOpponents.length > 0) {
const rankSum = teamFixture.futureOpponents.reduce((sum, opponentName) => sum + (teamRankings.get(opponentName) || 0), 0);
const fzValue = rankSum / teamFixture.futureOpponents.length;
fzCell.textContent = fzValue.toFixed(1);
fzCell.title = i18n.get('fzTooltip', {
remaining: teamFixture.futureOpponents.length,
rankSum: rankSum
});
} else if (fzCell) {
fzCell.textContent = '-';
fzCell.title = i18n.get('fzNoMatchesLeft');
}
});
}
// =================================================================================
// BÖLÜM: İÇ SAHA / DIŞ SAHA FİLTRELEME FONKSİYONLARI (YENİ EKLENDİ)
// =================================================================================
/**
* Maç programı sayfasındaki tüm OYNANMIŞ maçları ayrıştırır ve bir dizi olarak döndürür.
* Sonuçları `allPlayedMatchesCache` içinde saklar.
*/
function parseAllPlayedMatches() {
if (allPlayedMatchesCache) return allPlayedMatchesCache;
if (!fullScheduleCache || !fullScheduleCache.htmlText) return [];
const doc = new DOMParser().parseFromString(fullScheduleCache.htmlText, 'text/html');
const playedMatches = [];
const scoreRegex = /^\d+\s*-\s*\d+$/;
// Fikstür sayfalarındaki tüm maç satırlarını bul
doc.querySelectorAll('.matches_container table tbody tr, table.hitlist tbody tr').forEach(row => {
const cells = row.cells;
if (cells.length < 3) return;
const homeTeam = cells[0].textContent.trim();
const scoreText = cells[1].textContent.trim();
const awayTeam = cells[2].textContent.trim();
// Sadece skoru olan (oynanmış) maçları işle
if (scoreRegex.test(scoreText)) {
const scores = scoreText.split('-').map(s => parseInt(s.trim(), 10));
playedMatches.push({
homeTeam,
awayTeam,
homeGoals: scores[0],
awayGoals: scores[1]
});
}
});
allPlayedMatchesCache = playedMatches;
return playedMatches;
}
/**
* Verilen maç listesine ve filtre türüne göre puan durumunu hesaplar.
* @param {string[]} allTeams - Ligdeki tüm takımların adlarını içeren dizi.
* @param {object[]} allMatches - Oynanmış tüm maçların verilerini içeren dizi.
* @param {'home'|'away'} filterType - Hesaplanacak tablo türü.
* @returns {object} - Her takım için hesaplanmış istatistikleri içeren nesne.
*/
function calculateTableFromMatches(allTeams, allMatches, filterType) {
const standings = {};
// Tüm takımlar için istatistikleri sıfırla
allTeams.forEach(team => {
standings[team] = { p: 0, w: 0, d: 0, l: 0, gf: 0, ga: 0, played: 0 };
});
allMatches.forEach(match => {
const { homeTeam, awayTeam, homeGoals, awayGoals } = match;
// Maç sonucunu belirle
let homeResult, awayResult;
if (homeGoals > awayGoals) {
homeResult = { p: 3, w: 1, d: 0, l: 0 };
awayResult = { p: 0, w: 0, d: 0, l: 1 };
} else if (awayGoals > homeGoals) {
homeResult = { p: 0, w: 0, d: 0, l: 1 };
awayResult = { p: 3, w: 1, d: 0, l: 0 };
} else {
homeResult = { p: 1, w: 0, d: 1, l: 0 };
awayResult = { p: 1, w: 0, d: 1, l: 0 };
}
// Filtreye göre istatistikleri güncelle
if (filterType === 'home' && standings[homeTeam]) {
standings[homeTeam].played++;
standings[homeTeam].p += homeResult.p;
standings[homeTeam].w += homeResult.w;
standings[homeTeam].d += homeResult.d;
standings[homeTeam].l += homeResult.l;
standings[homeTeam].gf += homeGoals;
standings[homeTeam].ga += awayGoals;
}
if (filterType === 'away' && standings[awayTeam]) {
standings[awayTeam].played++;
standings[awayTeam].p += awayResult.p;
standings[awayTeam].w += awayResult.w;
standings[awayTeam].d += awayResult.d;
standings[awayTeam].l += awayResult.l;
standings[awayTeam].gf += awayGoals;
standings[awayTeam].ga += homeGoals;
}
});
return standings;
}
/**
* Hesaplanan yeni puan durumu verileriyle DOM'daki tabloyu günceller.
* @param {object} standings - Hesaplanmış istatistikleri içeren nesne.
*/
function updateTableDisplay(standings) {
const tableRows = document.querySelectorAll(CONFIG.SELECTORS.TABLE_ROWS);
tableRows.forEach(row => {
const teamLink = row.querySelector(CONFIG.SELECTORS.TEAM_LINK_IN_ROW);
if (!teamLink) return;
const teamName = teamLink.textContent.trim();
const stats = standings[teamName];
if (!stats) return;
const C = CONFIG.COLUMNS;
const cells = row.cells;
cells[C.PLAYED].textContent = stats.played;
cells[C.WINS].textContent = stats.w;
cells[C.DRAWS].textContent = stats.d;
cells[C.LOSSES].textContent = stats.l;
cells[C.GOALS_FOR].textContent = stats.gf;
cells[C.GOALS_AGAINST].textContent = stats.ga;
cells[C.GOAL_DIFFERENCE].textContent = stats.gf - stats.ga;
cells[C.POINTS].textContent = stats.p;
});
sortTableByPoints();
}
/**
* Filtre butonlarını oluşturur ve sayfaya ekler.
*/
function setupHomeAwayFilters() {
const titleHeader = findMainLeagueTableHeader();
if (!titleHeader || document.querySelector('.mz-table-filters')) return;
// Önceki denemelerden kalmış olabilecek eski konteynerleri temizle
const existingFilters = titleHeader.querySelector('.mz-table-filters');
if (existingFilters) existingFilters.remove();
const filterContainer = document.createElement('div');
filterContainer.className = 'mz-table-filters';
filterContainer.style.cssText = 'display: inline-flex; gap: 5px; margin-left: 15px; vertical-align: middle;';
const filters = [
{ type: 'all', text: i18n.get('overall') },
{ type: 'home', text: i18n.get('home') },
{ type: 'away', text: i18n.get('away') }
];
filters.forEach(filter => {
const buttonLink = document.createElement('a');
buttonLink.href = '#';
buttonLink.className = 'mzbtn buttondiv button_account';
buttonLink.dataset.filter = filter.type;
buttonLink.style.margin = '0';
// YENİ EKLENDİ: Butonun büyüme animasyonunu akıcı hale getirir.
buttonLink.style.transition = 'transform 0.15s ease-in-out';
buttonLink.innerHTML = `
<span class="buttonClassMiddle">
<span style="white-space: nowrap;">${filter.text}</span>
</span>
<span class="buttonClassRight"> </span>`;
if (filter.type === 'all') {
// ESKİ HALİ: buttonLink.style.filter = 'brightness(85%)';
// YENİ HALİ: Sayfa ilk açıldığında "Genel" butonu aktif ve büyük görünür.
buttonLink.style.transform = 'scale(1.05)';
buttonLink.style.zIndex = '1'; // Diğer butonların üzerine çıkması için
}
buttonLink.addEventListener('click', (e) => {
e.preventDefault();
handleFilterClick(filter.type);
});
filterContainer.appendChild(buttonLink);
});
titleHeader.appendChild(filterContainer);
}
/**
* Filtre butonuna tıklandığında tetiklenir. (Güncellenmiş Versiyon)
*/
function handleFilterClick(filterType) {
// Aktif buton stilini ayarla
document.querySelectorAll('.mz-table-filters .mzbtn').forEach(btn => {
// ESKİ MANTIK (Karartma):
// btn.style.filter = '';
// if (btn.dataset.filter === filterType) {
// btn.style.filter = 'brightness(85%)';
// }
// YENİ MANTIK (Büyütme):
// Önce tüm butonları varsayılan (küçük) haline geri getir.
btn.style.transform = 'scale(1)';
btn.style.zIndex = '0';
// Sadece tıklanan butonu büyüt ve öne çıkar.
if (btn.dataset.filter === filterType) {
btn.style.transform = 'scale(1.15)'; // Hafifçe büyüt
btn.style.zIndex = '1'; // Diğer butonların üzerine çıkmasını sağla
}
});
const currentTbody = document.querySelector(CONFIG.SELECTORS.TABLE_BODY);
if (!currentTbody || !originalTableBody) return;
if (filterType === 'all') {
currentTbody.parentNode.replaceChild(originalTableBody.cloneNode(true), currentTbody);
addFixtureDifficultyColumn();
toggleLogoVisibility();
calculateAndDisplayFixtureDifficulty();
if (myTeamInfo.id) {
performFullOpponentAnalysis();
}
return;
}
const allMatches = parseAllPlayedMatches();
if (!allMatches || allMatches.length === 0) return;
const allTeams = Array.from(originalTableBody.querySelectorAll(CONFIG.SELECTORS.TEAM_LINK_IN_ROW))
.map(link => link.textContent.trim());
const newStandings = calculateTableFromMatches(allTeams, allMatches, filterType);
updateTableDisplay(newStandings);
}
async function run() {
if (!isMainLeagueTableVisible()) {
return;
}
if (!originalTableBody) {
const tbody = document.querySelector(CONFIG.SELECTORS.TABLE_BODY);
if (tbody) originalTableBody = tbody.cloneNode(true);
}
toggleLogoVisibility();
addFixtureDifficultyColumn();
const myTeamRow = document.querySelector(CONFIG.SELECTORS.MY_TEAM_ROW);
const myTeamLink = myTeamRow?.querySelector(CONFIG.SELECTORS.TEAM_LINK_IN_ROW);
if (myTeamLink) {
myTeamInfo.name = myTeamLink.textContent.trim();
myTeamInfo.id = new URLSearchParams(myTeamLink.href).get('tid');
}
await fetchAndCacheSchedule();
await calculateAndDisplayFixtureDifficulty();
if (myTeamInfo.id) {
await performFullOpponentAnalysis();
}
setupLiveUpdateFeature();
setupSettingsMenu();
setupHomeAwayFilters();
}
async function initializeScript() {
isLogoDisplayEnabled = await GM_getValue('showTeamLogos', true);
injectStyles();
createSettingsModal();
NProgress.configure({
showSpinner: false
});
const contentArea = document.querySelector(CONFIG.SELECTORS.CONTENT_DIV);
if (!contentArea) return;
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length) {
if (isMainLeagueTableVisible() && !document.querySelector(`.${CONFIG.CLASSES.FZ_HEADER}`)) {
fullScheduleCache = null;
originalTableBody = null;
run();
break;
}
}
}
});
observer.observe(contentArea, {
childList: true,
subtree: true
});
run();
}
initializeScript();
}
/****************************************************************************************
* *
* BÖLÜM 2: OYUNCU İSTATİSTİK BETİĞİ (ManagerZone Player Stat Averages) *
* *
****************************************************************************************/
function initializePlayerStatsScript() {
// --- INTERNATIONALIZATION (i18n) ---
const translations = {
tr: {
// UI Buttons & Titles
autoScan: "Otomatik Tara",
statistics: "İstatistikler",
compare: "Karşılaştır",
clearData: "Verileri Sil",
compareAllPlayers: "Tüm Oyuncuları Karşılaştır",
autoScanOptions: "Otomatik Tarama Seçenekleri",
playerComparison: "Oyuncu Karşılaştırması",
scanSelectedCategories: (count) => `Seçilen ${count} Kategoride Taramayı Başlat`,
avgStatsForMatches: (count) => `Ortalama İstatistikler (${count} maç)`,
exportToExcel: "Excel'e Aktar",
exporting: "Dışa aktarılıyor...",
// UI Texts & Placeholders
scanInfo: "Aşağıdan istatistiklerini toplamak istediğiniz maç türlerini seçin.",
selectAll: "Tümünü Seç",
deselectAll: "Seçimi Kaldır",
// Popups & Alerts
loadingPlayerList: "Oyuncu listeniz oluşturuluyor, lütfen bekleyin...",
noDataFound: "Karşılaştırılacak oyuncu veya istatistik bulunamadı. Lütfen önce 'Oyuncular' sayfanızı ziyaret ederek listenizi oluşturun veya bir tarama yapın.",
confirmClearData: "Tüm oyuncu istatistikleri ve taranan maç kayıtları kalıcı olarak silinecek. Emin misiniz?",
allDataCleared: "Tüm istatistik verileri başarıyla temizlendi.",
noMatchTypes: "Taranacak maç tipi bulunamadı.",
confirmScan: (count) => `${count} farklı maç tipi taranacak. Bu işlem arka planda çalışacak ve biraz zaman alabilir. Devam edilsin mi?`,
scanStarting: "Tarama başlıyor...",
scanningCategory: (name, current, total) => `Kategori taranıyor: ${name} (${current}/${total})`,
processingNewMatch: (catIndex, catTotal, category, matchCurrent, matchTotal, totalScanned, totalOverall) => `Kategori: ${catIndex}/${catTotal} (${category}) | Maç: ${matchCurrent}/${matchTotal} | Toplam: ${totalScanned}/${totalOverall}`,
errorFetchingMatch: (mid) => `Hata: Maç ID ${mid} çekilemedi.`,
errorProcessingCategory: (name) => `Hata: ${name} işlenemedi. Sonrakine geçiliyor...`,
scanFinishedVerifying: "Tarama bitti, kadro listesi doğrulanıyor...",
scanSuccessNewMatches: "Otomatik tarama başarıyla tamamlandı! Yeni maç istatistikleri eklendi.",
scanSuccessNoNewMatches: "Tarama tamamlandı. Seçilen kategorilerde taranacak yeni maç bulunamadı.",
noPlayersToShow: "Gösterilecek oyuncu bulunamadı.",
noMatchesForCriteria: "Seçili kriterlere uygun maç bulunamadı.",
exportError: "Excel'e aktarma sırasında bir hata oluştu.",
// Filters & Table Headers
all: "Tümü",
none: "Yok",
league: "Lig",
under18: "Altı 18",
under21: "Altı 21",
under23: "Altı 23",
player: "Oyuncu",
age: "Yaş",
matches: "Maç",
totalSuffix: "(Toplam)",
avgSuffix: "(Ort.)",
// Stat Mappings (Türkçe)
statMappings: {
'MP': 'Oynadığı Süre (dk)', 'G': 'Gol', 'A': 'Asist', 'S': 'Şut', 'G%': 'Gol Yüzdesi',
'SOT': 'Kaleyi Bulan Şut', 'SOT%': 'Kaleyi Bulan Şut Yüzdesi', 'P%': 'Başarılı Pas Yüzdesi',
'PL': 'Ortalama Başarılı Pas Mesafesi (m)', 'In': 'Akın Kesme', 'T': 'Top Çalma',
'T%': 'Top Çalma Yüzdesi', 'Pos%': 'Tüm takım içinde topa sahip olma yüzdesi', 'Dist': 'Kat Edilen Mesafe (km)',
'DistP': 'Topla toplam katedilen mesafesi (km)', 'SpP': 'Topla ortalama hızı (km/s)',
'SpR': 'Koşarken ortalama hızı (km/s)', 'Pen': 'Penaltı', 'YC': 'Sarı Kart',
'RC': 'Kırmızı Kart', 'SV': 'Kurtarış'
},
// DEĞİŞİKLİK: Kategori değerlerini ve metinlerini eşleştirmek için
categoryValueMap: {
'league_senior': 'Lig', 'series': 'Lig', 'friendlyseries': 'Dostluk Ligi', 'world_series': 'Dünya Ligi',
'friendly': 'Hazırlık Maçı', 'cup': 'Resmi Kupa', 'u18_world_series': 'U18 Dünya Ligi',
'u21_world_series': 'U21 Dünya Ligi', 'u23_world_series': 'U23 Dünya Ligi', 'private_cup': 'Ödüllü/Dostluk Kupası'
}
},
en: {
// UI Buttons & Titles
autoScan: "Auto-Scan",
statistics: "Statistics",
compare: "Compare",
clearData: "Clear Data",
compareAllPlayers: "Compare All Players",
autoScanOptions: "Auto-Scan Options",
playerComparison: "Player Comparison",
scanSelectedCategories: (count) => `Start Scan for ${count} Selected Categories`,
avgStatsForMatches: (count) => `Average Statistics (${count} matches)`,
exportToExcel: "Export to Excel",
exporting: "Exporting...",
// UI Texts & Placeholders
scanInfo: "Select the match types you want to collect statistics from below.",
selectAll: "Select All",
deselectAll: "Deselect All",
// Popups & Alerts
loadingPlayerList: "Creating your player list, please wait...",
noDataFound: "No players or statistics found to compare. Please visit your 'Squad' page first to create your list or perform a scan.",
confirmClearData: "All player statistics and scanned match records will be permanently deleted. Are you sure?",
allDataCleared: "All statistics data has been successfully cleared.",
noMatchTypes: "No match types found to scan.",
confirmScan: (count) => `${count} different match types will be scanned. This process will run in the background and may take some time. Do you want to continue?`,
scanStarting: "Scan is starting...",
scanningCategory: (name, current, total) => `Scanning category: ${name} (${current}/${total})`,
processingNewMatch: (catIndex, catTotal, category, matchCurrent, matchTotal, totalScanned, totalOverall) => `Category: ${catIndex}/${catTotal} (${category}) | Match: ${matchCurrent}/${matchTotal} | Total: ${totalScanned}/${totalOverall}`,
errorFetchingMatch: (mid) => `Error: Could not fetch match ID ${mid}.`,
errorProcessingCategory: (name) => `Error: Could not process ${name}. Skipping to the next one...`,
scanFinishedVerifying: "Scan finished, verifying squad list...",
scanSuccessNewMatches: "Automated scan completed successfully! New match statistics have been added.",
scanSuccessNoNewMatches: "Scan completed. No new matches were found to scan in the selected categories.",
noPlayersToShow: "No players to display.",
noMatchesForCriteria: "No matches found for the selected criteria.",
exportError: "An error occurred during the Excel export.",
// Filters & Table Headers
all: "All",
none: "None",
league: "League",
under18: "Under 18",
under21: "Under 21",
under23: "Under 23",
player: "Player",
age: "Age",
matches: "Matches",
totalSuffix: "(Total)",
avgSuffix: "(Avg.)",
// Stat Mappings (English) - Based on the provided HTML
statMappings: {
'MP': 'Minutes played', 'G': 'Goals', 'A': 'Assists', 'S': 'Shots', 'G%': 'Goal percentage',
'SOT': 'Shots on target', 'SOT%': 'Shots on target percentage', 'P%': 'Successful pass percentage',
'PL': 'Average successful pass length (m)', 'In': 'Interceptions', 'T': 'Tackles',
'T%': 'Successful tackle percentage', 'Pos%': 'Team possession percentage', 'Dist': 'Distance covered (km)',
'DistP': 'Distance covered with ball (km)', 'SpP': 'Average speed with ball (km/h)',
'SpR': 'Average running speed (km/h)', 'Pen': 'Penalties', 'YC': 'Yellow Cards',
'RC': 'Red Cards', 'SV': 'Saves'
},
// DEĞİŞİKLİK: Kategori değerlerini ve metinlerini eşleştirmek için
categoryValueMap: {
'league_senior': 'League', 'series': 'League', 'friendlyseries': 'Friendly League', 'world_series': 'World League',
'friendly': 'Friendly', 'cup': 'Official Cup', 'u18_world_series': 'U18 World League',
'u21_world_series': 'U21 World League', 'u23_world_series': 'U23 World League', 'private_cup': 'Prized/Friendly Cup'
}
}
};
const detectedLang = $('meta[name="language"]').attr('content') || 'en';
const lang = detectedLang === 'tr' ? 'tr' : 'en';
const i18n = translations[lang];
function getShortCategoryName(categoryText) {
// DEĞİŞİKLİK BAŞLANGICI: i18n'den gelen metinlerle çalış
const trMap = translations.tr.categoryValueMap;
const enMap = translations.en.categoryValueMap;
// Metnin hangi haritadan geldiğini bul
if (Object.values(trMap).includes(categoryText)) {
if (categoryText === "Federasyon Çatışması Gözlemci Maçı") return "Fed. Gözlemci";
if (categoryText === "Federasyon Çatışması Mücadelesi") return "Fed. Mücadelesi";
if (categoryText === "Ödüllü/Dostluk Kupası") return "Ödüllü/Dostluk";
} else if (Object.values(enMap).includes(categoryText)) {
if (categoryText === "Federation Clash Scout Match") return "Fed. Scout";
if (categoryText === "Federation Clash Challenge") return "Fed. Challenge";
if (categoryText === "Prized/Friendly Cup") return "Prized/Friendly";
if (categoryText === "Federationsclash: Scoutmatch") return "Fed. Scout";
if (categoryText === "Federationsclash: Utmaning") return "Fed. Utmaning";
}
return categoryText;
// DEĞİŞİKLİK SONU
}
// Global variable
let squadPlayersWithStats = [];
// --- Helper Functions ---
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// --- Style Definitions ---
GM_addStyle(`
.mz-script-button {
padding: 8px 15px; border: none; border-radius: 5px; cursor: pointer;
font-size: 14px; font-weight: bold; color: white; text-align: center;
transition: background-color 0.2s, opacity 0.2s; vertical-align: middle; margin: 5px;
}
.mz-script-button:disabled { background-color: #6c757d !important; opacity: 0.7; cursor: not-allowed; }
.mz-script-button:hover:not(:disabled) { filter: brightness(110%); }
#comparePlayersButton { background-color: #17a2b8; }
#player-stats-popup, #player-comparison-popup, #scrape-options-popup {
position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
background-color: #f8f9fa; border: 2px solid #343a40; border-radius: 10px;
padding: 15px; z-index: 10000; box-shadow: 0 5px 15px rgba(0,0,0,0.3);
color: #212529; font-family: Dosis, sans-serif;
width: 95vw; max-width: 1500px; max-height: 85vh;
overflow: hidden; display: flex; flex-direction: column; box-sizing: border-box;
}
.popup-content-scrollable { overflow-y: auto; flex-grow: 1; padding-right: 5px; }
.popup-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #dee2e6; padding-bottom: 10px; margin-bottom: 15px; flex-shrink: 0; }
.popup-header h3 { margin: 0; color: #00529B; font-size: 1.1rem; }
.popup-filters { display: flex; gap: 10px; margin-bottom: 15px; flex-wrap: wrap; flex-shrink: 0; align-items: center; }
.popup-filters select, #player-switcher { padding: 8px; border-radius: 4px; border: 1px solid #ccc; background-color: white; }
#player-switcher { margin-right: 15px; }
#player-stats-popup table, #player-comparison-popup table { width: 100%; border-collapse: collapse; font-size: 13px; }
#player-stats-popup th, #player-stats-popup td, #player-comparison-popup th, #player-comparison-popup td { border: 1px solid #dee2e6; padding: 8px; text-align: left; white-space: nowrap; }
#player-stats-popup th, #player-comparison-popup th { background-color: #e9ecef; }
[data-tooltip] { cursor: help !important; }
#player-comparison-popup .sort-asc::after { content: ' ▲'; color: #007bff; }
#player-comparison-popup .sort-desc::after { content: ' ▼'; color: #007bff; }
#player-comparison-popup .sort-sum-desc::after { content: ' (T ▼)'; color: #dc3545; }
#player-comparison-popup .sort-sum-asc::after { content: ' (T ▲)'; color: #dc3545; }
#player-comparison-popup .sort-avg-desc::after { content: ' (O ▼)'; color: #28a745; }
#player-comparison-popup .sort-avg-asc::after { content: ' (O ▲)'; color: #28a745; }
.table-responsive-wrapper { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; border: 1px solid #ddd; border-radius: 5px; }
.popup-close { font-size: 28px; font-weight: bold; cursor: pointer; color: #adb5bd; line-height: 1; }
#mz-stat-loader { position: fixed; top: 0; left: 0; width: 100%; background: #28a745; color: white; padding: 10px 0; z-index: 10001; font-weight: bold; box-shadow: 0 2px 5px rgba(0,0,0,0.2); text-align: center; font-size: 16px; display: none; }
#custom-tooltip { position: fixed; display: none; background-color: #222; color: white; padding: 8px 12px; border-radius: 5px; z-index: 10005; font-size: 12px; max-width: 250px; text-align: center; pointer-events: none; }
.colored-cell { color: black; font-weight: bold; }
#mz-stats-actions-container { display: inline-flex; gap: 8px; margin-left: 15px; vertical-align: middle; flex-wrap: wrap; justify-content: center; }
#mz-stats-actions-container button {
padding: 8px 12px; border: none; border-radius: 5px; cursor: pointer; font-size: 12px;
font-weight: bold; color: white; text-align: center; transition: opacity 0.2s, background-color 0.2s;
}
#mz-stats-actions-container button:disabled { background-color: #6c757d !important; opacity: 0.7; cursor: not-allowed; }
#scrapeStatsButton { background-color: #007bff; }
#viewPlayerStatsButton { background-color: #fd7e14; }
#comparePlayersButtonMatchPage { background-color: #17a2b8; }
#clearAllStatsButton { background-color: #dc3545; }
#scrape-options-list { list-style-type: none; padding: 0; margin: 0 0 15px 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; max-height: 300px; overflow-y: auto; }
#scrape-options-list li { background: #e9ecef; padding: 8px; border-radius: 4px; }
#scrape-options-list label { display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; }
.scrape-options-controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
#start-scrape-button { background-color: #28a745; color: white; padding: 12px 20px; font-size: 16px; border: none; border-radius: 5px; cursor: pointer; width: 100%; }
#player-stats-popup {
width: auto;
min-width: 500px;
}
#popup-stats-table {
margin: 0 auto;
}
.dynamic-stats-container {
position: relative;
display: inline-flex;
align-items: center;
vertical-align: middle;
margin-left: 4px;
white-space: nowrap;
}
.stats-display-area {
display: inline-flex;
align-items: center;
gap: 5px;
cursor: pointer;
font-size: 11px;
}
.stats-display-area .category-name {
font-weight: bold;
color: #00529B;
}
.stats-display-area .stats-values span {
display: inline-flex;
align-items: center;
gap: 2px;
margin-left: 4px;
}
.stats-display-area img {
width: 10px;
height: 10px;
vertical-align: middle;
}
.assist-icon {
font-family: "Arial", sans-serif;
font-weight: bold;
font-size: 14px;
color: #28a745;
vertical-align: middle;
line-height: 1;
}
.stats-category-dropdown {
display: none;
position: absolute;
top: 100%;
left: 0;
background-color: white;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
z-index: 10010;
list-style-type: none;
padding: 5px 0;
margin: 2px 0 0 0;
max-height: 200px;
overflow-y: auto;
}
.stats-category-dropdown li {
padding: 6px 15px;
cursor: pointer;
font-size: 13px;
white-space: nowrap;
}
.stats-category-dropdown li:hover {
background-color: #f0f0f0;
}
@media (max-width: 768px) {
.popup-filters { flex-direction: column; align-items: stretch; }
#player-switcher { margin: 10px 0; }
.popup-header h3 { font-size: 1rem; }
}
`);
// --- Page Router ---
const params = new URLSearchParams(window.location.search);
const p = params.get('p');
const sub = params.get('sub');
if (p === 'match' && sub === 'played') {
initMatchPage();
} else if (p === 'players' || (p === 'player' && params.has('pid'))) {
initPlayersPage();
}
// --- Core Functions ---
async function updateUserPlayerList() {
const players = [];
$('.playerContainer').each(function() {
const pid = $(this).find('span.player_id_span').text();
const name = $(this).find('h2.subheader a .player_name').text().trim();
if (pid && name) {
const ageText = $(this).find('.dg_playerview_info table tr:first-child td:first-child').text();
let age = parseInt(ageText.replace(/[^0-9]/g, ''));
if (isNaN(age)) {
age = 0;
}
players.push({
pid: pid,
name: name,
age: age
});
}
});
if (players.length > 0) {
await GM_setValue('myPlayerList', JSON.stringify(players));
}
}
async function populateSquadPlayers() {
const myPlayerList = JSON.parse(await GM_getValue('myPlayerList', '[]'));
if (myPlayerList.length > 0) {
squadPlayersWithStats = [...myPlayerList];
squadPlayersWithStats.sort((a, b) => a.name.localeCompare(b.name, lang === 'tr' ? 'tr-TR' : undefined));
} else {
squadPlayersWithStats = [];
}
}
async function refreshUserPlayerListFromSource() {
try {
const response = await fetch('https://www.managerzone.com/?p=players');
if (!response.ok) {
return false;
}
const html = await response.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
const players = [];
$(doc).find('.playerContainer').each(function() {
const pid = $(this).find('span.player_id_span').text();
const name = $(this).find('h2.subheader a .player_name').text().trim();
if (pid && name) {
const ageText = $(this).find('.dg_playerview_info table tr:first-child td:first-child').text();
let age = parseInt(ageText.replace(/[^0-9]/g, ''));
if (isNaN(age)) {
age = 0;
}
players.push({
pid: pid,
name: name,
age: age
});
}
});
if (players.length > 0) {
await GM_setValue('myPlayerList', JSON.stringify(players));
return true;
} else {
return false;
}
} catch (error) {
return false;
}
}
// =================================================================== //
// --- MATCH PAGE FUNCTIONS --- //
// =================================================================== //
async function initMatchPage() {
$('body').append('<div id="custom-tooltip"></div>').append(`<div id="mz-stat-loader"></div>`);
setupTooltipListeners();
const targetElement = $('#squad-search-toggle').length ? $('#squad-search-toggle') : $('.match-info-r1.block').first();
if (targetElement.length > 0) {
const actionsContainer = $('<div id="mz-stats-actions-container"></div>');
targetElement.after(actionsContainer);
createScrapeButton(actionsContainer);
createMatchPageActionButtons(actionsContainer);
}
}
function createScrapeButton(container) {
$(`<button id="scrapeStatsButton" class="mz-script-button">${i18n.autoScan}</button>`)
.appendTo(container)
.on('click', showMatchTypeSelectionPopup);
}
async function ensurePlayerListIsPopulated() {
await populateSquadPlayers();
if (squadPlayersWithStats.length === 0) {
const loader = $('#mz-stat-loader');
loader.text(i18n.loadingPlayerList).show();
const success = await refreshUserPlayerListFromSource();
loader.hide();
if (success) {
await populateSquadPlayers();
}
}
if (squadPlayersWithStats.length === 0) {
alert(i18n.noDataFound);
return false;
}
return true;
}
async function updateMatchPageButtonStates() {
const statsData = await GM_getValue('playerStatsRaw', '{}');
const hasData = Object.keys(JSON.parse(statsData)).length > 0;
$('#viewPlayerStatsButton').prop('disabled', !hasData);
$('#comparePlayersButtonMatchPage').prop('disabled', !hasData);
$('#clearAllStatsButton').prop('disabled', !hasData);
}
function createMatchPageActionButtons(container) {
container.find('#viewPlayerStatsButton, #comparePlayersButtonMatchPage, #clearAllStatsButton').remove();
$(`<button id="viewPlayerStatsButton" class="mz-script-button">${i18n.statistics}</button>`)
.appendTo(container)
.on('click', async () => {
if (await ensurePlayerListIsPopulated()) showStatsPopup(squadPlayersWithStats[0].pid);
});
$(`<button id="comparePlayersButtonMatchPage" class="mz-script-button">${i18n.compare}</button>`)
.appendTo(container)
.on('click', async () => {
if (await ensurePlayerListIsPopulated()) showAllPlayersComparisonPopup();
});
$(`<button id="clearAllStatsButton" class="mz-script-button">${i18n.clearData}</button>`)
.appendTo(container)
.on('click', async () => {
if (confirm(i18n.confirmClearData)) {
await GM_deleteValue('playerStatsRaw');
await GM_deleteValue('processedMatchIds');
await updateMatchPageButtonStates();
}
});
updateMatchPageButtonStates();
}
function showMatchTypeSelectionPopup() {
$('#scrape-options-popup').remove();
const matchTypes = [];
$('select[name="selectType"] optgroup:first option').each(function() {
matchTypes.push({
value: $(this).val(),
text: $(this).text().trim()
});
});
if (matchTypes.length === 0) {
alert(i18n.noMatchTypes);
return;
}
const optionsHTML = matchTypes.map(mt => `<li><label><input type="checkbox" class="match-type-checkbox" value="${mt.value}" data-text="${mt.text}" checked> ${mt.text}</label></li>`).join('');
const popupHTML = `
<div id="scrape-options-popup">
<div class="popup-header"><h3>${i18n.autoScanOptions}</h3><span class="popup-close">×</span></div>
<div class="popup-content-scrollable">
<p>${i18n.scanInfo}</p>
<div class="scrape-options-controls"><a href="#" id="select-all-types">${i18n.selectAll}</a><a href="#" id="deselect-all-types">${i18n.deselectAll}</a></div>
<ul id="scrape-options-list">${optionsHTML}</ul>
<button id="start-scrape-button">${i18n.scanSelectedCategories(matchTypes.length)}</button>
</div>
</div>`;
$('body').append(popupHTML);
$('.popup-close').on('click', () => $('#scrape-options-popup').remove());
$('#select-all-types').on('click', (e) => {
e.preventDefault();
$('.match-type-checkbox').prop('checked', true).trigger('change');
});
$('#deselect-all-types').on('click', (e) => {
e.preventDefault();
$('.match-type-checkbox').prop('checked', false).trigger('change');
});
$('.match-type-checkbox').on('change', () => {
const count = $('.match-type-checkbox:checked').length;
$('#start-scrape-button').text(i18n.scanSelectedCategories(count)).prop('disabled', count === 0);
});
$('#start-scrape-button').on('click', function() {
const selectedTypes = [];
$('.match-type-checkbox:checked').each(function() {
selectedTypes.push({
value: $(this).val(),
text: $(this).data('text')
});
});
if (selectedTypes.length > 0) {
$('#scrape-options-popup').remove();
startAutomatedScrape(selectedTypes);
}
$(this).prop('disabled', true);
});
}
async function startAutomatedScrape(selectedTypes) {
const loader = $('#mz-stat-loader');
loader.text(i18n.scanStarting).show();
const canonicalStatHeaders = [
null, 'Po', 'MP', 'G', 'A', 'S', 'G%', 'SOT', 'SOT%', 'OG', 'P', 'P_S', 'P_F',
'P%', 'PL', 'In', 'T_raw', 'T_S', 'T_F', 'T%', 'PosTime', 'Pos%', 'Dist',
'DistP', 'SpP', 'SpR', 'Cr', 'Fk', 'Pen', 'YC', 'RC', 'SV'
];
let newMatchesFound = false,
playerStats = JSON.parse(await GM_getValue('playerStatsRaw', '{}')),
processedMatchIds = new Set(JSON.parse(await GM_getValue('processedMatchIds', '[]'))),
myPlayerList = JSON.parse(await GM_getValue('myPlayerList', '[]')),
myPlayerPids = new Set(myPlayerList.map(p => p.pid));
let totalMatchesToScan = 0;
let totalMatchesScanned = 0;
loader.text(lang === 'tr' ? 'Taranacak toplam maç sayısı hesaplanıyor...' : 'Calculating total matches to scan...').show();
for (const category of selectedTypes) {
try {
const categoryUrl = `https://www.managerzone.com/?p=match&sub=played&selectType=${category.value}&selectTimeLimit=maximum`;
const categoryResponse = await fetch(categoryUrl);
const categoryHtml = await categoryResponse.text();
const categoryDoc = new DOMParser().parseFromString(categoryHtml, 'text/html');
const uniqueLinksMap = new Map();
$(categoryDoc).find('#fixtures-results-list a.gradientSunriseIcon[href*="sub=stats"]').each(function() {
const href = $(this).attr('href'),
mid = new URLSearchParams(href.split('?')[1]).get('mid');
if (mid && !uniqueLinksMap.has(mid)) uniqueLinksMap.set(mid, { mid: mid });
});
const newLinksToScrape = Array.from(uniqueLinksMap.values()).filter(linkInfo => !processedMatchIds.has(linkInfo.mid));
totalMatchesToScan += newLinksToScrape.length;
} catch (e) {
console.error(`Ön tarama hatası: ${category.text}`, e);
}
}
for (const [categoryIndex, category] of selectedTypes.entries()) {
loader.text(i18n.scanningCategory(category.text, categoryIndex + 1, selectedTypes.length));
try {
const categoryUrl = `https://www.managerzone.com/?p=match&sub=played&selectType=${category.value}&selectTimeLimit=maximum`;
const categoryResponse = await fetch(categoryUrl);
const categoryHtml = await categoryResponse.text();
const categoryDoc = new DOMParser().parseFromString(categoryHtml, 'text/html');
const uniqueLinksMap = new Map();
$(categoryDoc).find('#fixtures-results-list a.gradientSunriseIcon[href*="sub=stats"]').each(function() {
const href = $(this).attr('href'),
mid = new URLSearchParams(href.split('?')[1]).get('mid');
if (mid && !uniqueLinksMap.has(mid)) uniqueLinksMap.set(mid, {
href: href,
mid: mid
});
});
const newLinksToScrape = Array.from(uniqueLinksMap.values()).filter(linkInfo => !processedMatchIds.has(linkInfo.mid));
if (newLinksToScrape.length > 0) {
newMatchesFound = true;
for (const [linkIndex, linkInfo] of newLinksToScrape.entries()) {
totalMatchesScanned++;
loader.text(i18n.processingNewMatch(
categoryIndex + 1,
selectedTypes.length,
category.text,
linkIndex + 1,
newLinksToScrape.length,
totalMatchesScanned,
totalMatchesToScan
));
try {
const matchUrl = new URL('https://www.managerzone.com/');
matchUrl.search = new URLSearchParams(linkInfo.href.split('?')[1]);
const matchResponse = await fetch(matchUrl.toString());
const matchHtml = await matchResponse.text();
const matchDoc = new DOMParser().parseFromString(matchHtml, 'text/html');
let ageLimit = i18n.none;
const categoryTextLower = category.text.toLowerCase();
if (categoryTextLower.includes('18')) ageLimit = i18n.under18;
else if (categoryTextLower.includes('21')) ageLimit = i18n.under21;
else if (categoryTextLower.includes('23')) ageLimit = i18n.under23;
$(matchDoc).find('.matchStats--detailed tbody tr').each(function() {
const playerLink = $(this).find('a.player_link');
if (playerLink.length) {
const pid = new URLSearchParams(playerLink.attr('href').split('?')[1]).get('pid'),
name = playerLink.text().trim();
if (!myPlayerPids.has(pid)) {
myPlayerList.push({
pid,
name,
age: 0
});
myPlayerPids.add(pid);
}
if (!playerStats[pid]) playerStats[pid] = {
name: name,
matches: []
};
playerStats[pid].name = name;
const currentMatchStats = {};
$(this).find('td').each(function(colIndex) {
const statName = canonicalStatHeaders[colIndex];
if (statName && !['Po', 'OG', 'Cr', 'Fk', 'P', 'P_S', 'P_F', 'T_raw', 'T_S', 'T_F', 'PosTime'].includes(statName)) {
const rawValue = $(this).text().trim().replace('%', '').replace(',', '.').replace("'", "");
let numValue = parseFloat(rawValue);
if (statName === 'T' && currentMatchStats['T%'] !== undefined) {
} else if (!isNaN(numValue)) {
currentMatchStats[statName] = numValue;
}
}
});
if (Object.keys(currentMatchStats).length > 0) {
// DEĞİŞİKLİK BAŞLANGICI: Evrensel Filtreleme için `matchTypeValue` ekle
playerStats[pid].matches.push({
matchId: linkInfo.mid,
matchType: category.text, // Görüntüleme için
matchTypeValue: category.value, // Filtreleme için
ageLimit: ageLimit,
stats: currentMatchStats
});
// DEĞİŞİKLİK SONU
}
}
});
processedMatchIds.add(linkInfo.mid);
} catch (error) {
console.error(i18n.errorFetchingMatch(linkInfo.mid), error);
}
await sleep(250);
}
} else {
await sleep(500);
}
} catch (error) {
loader.text(i18n.errorProcessingCategory(category.text));
await sleep(2000);
}
}
await GM_setValue('playerStatsRaw', JSON.stringify(playerStats));
await GM_setValue('processedMatchIds', JSON.stringify(Array.from(processedMatchIds)));
loader.text(i18n.scanFinishedVerifying);
await refreshUserPlayerListFromSource();
await populateSquadPlayers();
loader.hide();
await updateMatchPageButtonStates();
$('#start-scrape-button').prop('disabled', false);
}
// ========================================================================= //
// --- PLAYER PAGE FUNCTIONS --- //
// ========================================================================= //
const displayColumns = ['MP', 'G', 'A', 'S', 'G%', 'SOT', 'SOT%', 'P%', 'PL', 'In', 'T%', 'Pos%', 'Dist', 'DistP', 'SpP', 'SpR', 'YC', 'RC', 'SV'];
const sumAvgColumns = ['G', 'A', 'YC', 'RC'];
// DEĞİŞİKLİK BAŞLANGICI: Fonksiyonu `matchTypeValue` kullanacak şekilde güncelle
function calculateAverages(playerData, filterTypeValue, filterAge) {
if (!playerData || !playerData.matches) return null;
const filteredMatches = playerData.matches.filter(match =>
(filterTypeValue === 'all' || match.matchTypeValue === filterTypeValue) &&
(filterAge === i18n.all || match.ageLimit === filterAge)
);
// DEĞİŞİKLİK SONU
if (filteredMatches.length === 0) return {
name: playerData.name,
matches: 0
};
const totalStats = {},
statCounts = {};
filteredMatches.forEach(match => {
for (const key in match.stats) {
totalStats[key] = (totalStats[key] || 0) + match.stats[key];
statCounts[key] = (statCounts[key] || 0) + 1;
}
});
const summary = {};
for (const key in totalStats) {
if (sumAvgColumns.includes(key)) {
summary[`${key}_sum`] = totalStats[key];
summary[`${key}_avg`] = (totalStats[key] / filteredMatches.length).toFixed(2);
} else {
summary[key] = (totalStats[key] / statCounts[key]).toFixed(2);
}
}
return {
name: playerData.name,
matches: filteredMatches.length,
...summary
};
}
async function initPlayersPage() {
$('body').append('<div id="custom-tooltip"></div>');
if ($('.playerContainer').length > 0) {
await updateUserPlayerList();
$(`<button id="comparePlayersButton" class="mz-script-button">${i18n.compareAllPlayers}</button>`)
.insertAfter('#squad-search-toggle')
.on('click', showAllPlayersComparisonPopup);
}
await populateSquadPlayers();
await injectStatsIcons();
setupTooltipListeners();
$(document).on('click', '.stats-button', function() {
showStatsPopup($(this).data('pid'));
});
$(document).on('click', '.stats-display-area', function(e) {
e.stopPropagation();
$('.stats-category-dropdown').not($(this).siblings('.stats-category-dropdown')).hide();
$(this).siblings('.stats-category-dropdown').toggle();
});
// DEĞİŞİKLİK BAŞLANGICI: `data-value` niteliğini kullan
$(document).on('click', '.stats-category-dropdown li', async function(e) {
e.stopPropagation();
const selectedCategoryValue = $(this).data('value'); // Filtreleme için value'yu al
const selectedCategoryText = $(this).text(); // Görüntüleme için metni al
const container = $(this).closest('.dynamic-stats-container');
const pid = container.data('pid');
const rawData = JSON.parse(await GM_getValue('playerStatsRaw', '{}'));
const stats = getCategorySummaryStats(pid, selectedCategoryValue, rawData); // value ile çağır
const displayArea = container.find('.stats-display-area');
displayArea.find('.category-name').text(getShortCategoryName(selectedCategoryText)); // Kısa metni göster
displayArea.find('.stat-g').text(stats.G);
displayArea.find('.stat-a').text(stats.A);
displayArea.find('.stat-yc').text(stats.YC);
displayArea.find('.stat-rc').text(stats.RC);
$(this).parent().hide();
});
// DEĞİŞİKLİK SONU
$(document).on('click', function() {
$('.stats-category-dropdown').hide();
});
}
async function injectStatsIcons() {
await populateSquadPlayers();
const rawData = JSON.parse(await GM_getValue('playerStatsRaw', '{}'));
if (Object.keys(rawData).length === 0) return;
const myPlayerPidsWithStats = new Set(Object.keys(rawData));
const statsButtonHTML = `<button class="stats-button" style="padding: 2px 8px; font-size: 12px; margin-left: 10px; vertical-align: middle; background-color: #007bff; color: white; border: 1px solid #006fe6; border-radius: 12px; cursor: pointer;">${i18n.statistics}</button>`;
const injectContent = (container) => {
const pid = container.find('span.player_id_span').text().trim() || new URLSearchParams(window.location.search).get('pid');
if (!pid) return;
const isSinglePlayerPage = container.is('div.player-title');
const headerTarget = isSinglePlayerPage ? container.find('h2') : container.find('h2.subheader a');
if (myPlayerPidsWithStats.has(pid) && headerTarget.parent().find('.stats-button').length === 0) {
$(statsButtonHTML).data('pid', pid).insertAfter(headerTarget);
}
const statsTd = container.find('td[colspan="2"][style*="padding-top: 12px;"]');
if (statsTd.length > 0 && statsTd.find('.dynamic-stats-container').length === 0 && myPlayerPidsWithStats.has(pid)) {
const playerData = rawData[pid];
if (!playerData || !playerData.matches || playerData.matches.length === 0) return;
// DEĞİŞİKLİK BAŞLANGICI: Kategorileri value ve text olarak ayır
const availableCategories = new Map();
playerData.matches.forEach(m => {
if (m.matchTypeValue && !availableCategories.has(m.matchTypeValue)) {
availableCategories.set(m.matchTypeValue, m.matchType);
}
});
if (availableCategories.size === 0) return;
let sortedCategories = Array.from(availableCategories.entries()).sort((a,b) => a[1].localeCompare(b[1]));
sortedCategories.unshift(['all', i18n.all]);
const defaultCategoryValue = 'all';
const defaultCategoryText = i18n.all;
// DEĞİŞİKLİK SONU
const defaultStats = getCategorySummaryStats(pid, defaultCategoryValue, rawData);
// DEĞİŞİKLİK: `data-value` ve `data-category` ekle
const dropdownItemsHTML = sortedCategories.map(([value, text]) => `<li data-value="${value}">${getShortCategoryName(text)}</li>`).join('');
const dynamicStatsHTML = `
<div class="dynamic-stats-container" data-pid="${pid}">
<div class="stats-display-area" title="İstatistik kategorisini değiştirmek için tıklayın">
<span class="category-name">${getShortCategoryName(defaultCategoryText)}</span><span style="font-weight: bold; font-size: 10px; margin-left: 2px;">▼</span>
<div class="stats-values">
<span title="${i18n.statMappings['G']}"><img src="/nocache-936/img/soccer/goal.gif"> <strong class="stat-g">${defaultStats.G}</strong></span>
<span title="${i18n.statMappings['A']}"><span class="assist-icon">👟</span> <strong class="stat-a">${defaultStats.A}</strong></span>
<span title="${i18n.statMappings['YC']}"><img src="/nocache-936/img/card_yellow.gif"> <strong class="stat-yc">${defaultStats.YC}</strong></span>
<span title="${i18n.statMappings['RC']}"><img src="/nocache-936/img/card_red.gif"> <strong class="stat-rc">${defaultStats.RC}</strong></span>
</div>
</div>
<ul class="stats-category-dropdown">${dropdownItemsHTML}</ul>
</div>
`;
statsTd.find('img[src*="goal.gif"], img[src*="card_yellow.gif"], img[src*="card_red.gif"], br').remove();
statsTd.contents().filter(function() { return this.nodeType === 3; }).remove();
statsTd.prepend(dynamicStatsHTML);
}
};
if ($('.playerContainer').length > 0) {
$('.playerContainer').each(function() {
injectContent($(this));
});
} else if (window.location.href.includes('p=player&pid=')) {
injectContent($('div.player-title').closest('.mainContent'));
}
}
// DEĞİŞİKLİK BAŞLANGICI: `matchTypeValue` ile filtreleme yapacak şekilde güncelle
function getCategorySummaryStats(pid, filterTypeValue, rawData) {
const playerData = rawData[pid];
if (!playerData || !playerData.matches) return { G: 0, A: 0, YC: 0, RC: 0 };
const filteredMatches = playerData.matches.filter(match => {
if (!match.matchTypeValue) return false;
if (filterTypeValue === 'all') return true;
if (filterTypeValue === 'league') { // "Lig" için özel filtreleme
return match.matchTypeValue.includes('league') || match.matchTypeValue.includes('series');
}
return match.matchTypeValue === filterTypeValue;
});
// DEĞİŞİKLİK SONU
if (filteredMatches.length === 0) return { G: 0, A: 0, YC: 0, RC: 0 };
return filteredMatches.reduce((acc, match) => {
acc.G += match.stats.G || 0;
acc.A += match.stats.A || 0;
acc.YC += match.stats.YC || 0;
acc.RC += match.stats.RC || 0;
return acc;
}, { G: 0, A: 0, YC: 0, RC: 0 });
}
async function showStatsPopup(pid) {
$('#player-stats-popup').remove();
await populateSquadPlayers();
if (squadPlayersWithStats.length === 0) {
alert(i18n.noPlayersToShow);
return;
}
const rawData = JSON.parse(await GM_getValue('playerStatsRaw', '{}'));
const playerOptionsHTML = squadPlayersWithStats.map(player => `<option value="${player.pid}" ${player.pid === pid ? 'selected' : ''}>${player.name}</option>`).join('');
const popupHTML = `
<div id="player-stats-popup">
<div class="popup-header">
<h3 id="popup-title"></h3><span class="popup-close">×</span>
</div>
<div class="popup-content-scrollable">
<div style="display: flex; align-items: center;">
<select id="player-switcher">${playerOptionsHTML}</select>
</div>
<div id="popup-category-selector" class="popup-filters" style="flex-direction: column; align-items: stretch; border: 1px solid #ccc; border-radius: 5px; padding: 5px; cursor: pointer; position: relative; margin-top: 15px;">
<div id="category-display-wrapper" style="display: flex; justify-content: space-between; align-items: center;">
<span id="current-category-name" style="font-weight: bold; color: #00529B;"></span>
<div id="current-category-stats" style="display: flex; gap: 10px; align-items: center; font-size: 12px;"></div>
<span id="category-dropdown-trigger" style="font-weight: bold; padding: 0 5px;">▼</span>
</div>
<ul id="category-dropdown-list" style="display: none; position: absolute; top: 100%; left: -1px; right: -1px; background: white; border: 1px solid #ccc; list-style: none; padding: 0; margin: 5px 0 0; z-index: 10001; max-height: 200px; overflow-y: auto; border-radius: 0 0 5px 5px;">
</ul>
</div>
<table id="popup-stats-table" style="margin-top:15px;"><thead><tr><th>${i18n.statistics}</th><th>Value</th></tr></thead><tbody></tbody></table>
</div>
</div>`;
$('body').append(popupHTML);
const updatePopupForPlayer = (currentPid) => {
const playerData = rawData[currentPid];
const availableCategories = new Map();
if (playerData && playerData.matches) {
playerData.matches.forEach(m => { if(m.matchTypeValue) availableCategories.set(m.matchTypeValue, m.matchType) });
}
const sortedCategories = Array.from(availableCategories.entries()).sort((a,b) => a[1].localeCompare(b[1]));
if (sortedCategories.length > 0) {
sortedCategories.unshift(['all', i18n.all]);
const categoryDropdown = $('#category-dropdown-list').empty();
sortedCategories.forEach(([value, text]) => {
categoryDropdown.append(`<li data-value="${value}" style="padding: 8px 12px; cursor: pointer;">${getShortCategoryName(text)}</li>`);
});
$('#popup-category-selector').show();
const defaultCategoryValue = 'all';
$('#popup-category-selector').attr('data-selected-category', defaultCategoryValue);
$('#current-category-name').text(getShortCategoryName(i18n.all));
updateCategoryStatsDisplay(currentPid, defaultCategoryValue, rawData);
updateStatsPopupTable(currentPid, defaultCategoryValue);
} else {
$('#popup-category-selector').hide();
updateStatsPopupTable(currentPid, 'all');
}
};
const updateCategoryStatsDisplay = (currentPid, categoryValue, rawData) => {
const stats = getCategorySummaryStats(currentPid, categoryValue, rawData);
$('#current-category-stats').html(`
<span title="${i18n.statMappings['G']}"><img src="/nocache-936/img/soccer/goal.gif" style="width:10px; vertical-align: middle;"> ${stats.G}</span>
<span title="${i18n.statMappings['A']}"><span class="assist-icon" style="vertical-align: middle;">👟</span> ${stats.A}</span>
<span title="${i18n.statMappings['YC']}"><img src="/nocache-936/img/card_yellow.gif" style="width:8px; vertical-align: middle;"> ${stats.YC}</span>
<span title="${i18n.statMappings['RC']}"><img src="/nocache-936/img/card_red.gif" style="width:8px; vertical-align: middle;"> ${stats.RC}</span>
`);
};
$('#player-switcher').on('change', function() { updatePopupForPlayer($(this).val()); });
$('#popup-category-selector').on('click', (e) => { e.stopPropagation(); $('#category-dropdown-list').slideToggle(200); });
$('#category-dropdown-list').on('mouseenter', 'li', function(){ $(this).css('background-color', '#f0f0f0'); });
$('#category-dropdown-list').on('mouseleave', 'li', function(){ $(this).css('background-color', 'white'); });
$('#category-dropdown-list').on('click', 'li', function(e) {
e.stopPropagation();
const selectedCategoryValue = $(this).data('value');
const selectedCategoryText = $(this).text();
$('#popup-category-selector').attr('data-selected-category', selectedCategoryValue);
$('#current-category-name').text(getShortCategoryName(selectedCategoryText));
const currentPid = $('#player-switcher').val();
updateCategoryStatsDisplay(currentPid, selectedCategoryValue, rawData);
updateStatsPopupTable(currentPid, selectedCategoryValue);
$('#category-dropdown-list').hide();
});
$(document).on('click.statsPopup', function(e) { if (!$('#popup-category-selector').is(e.target) && $('#popup-category-selector').has(e.target).length === 0) { $('#category-dropdown-list').slideUp(200); } });
$('#player-stats-popup .popup-close').on('click', () => { $('#player-stats-popup').remove(); $(document).off('click.statsPopup'); });
updatePopupForPlayer(pid);
}
async function updateStatsPopupTable(pid, filterTypeValue) {
$('#player-stats-popup').data('pid', pid);
const rawData = JSON.parse(await GM_getValue('playerStatsRaw', '{}'));
let data = calculateAverages(rawData[pid], filterTypeValue, i18n.all);
const tableBody = $('#popup-stats-table tbody').empty();
if (!data) {
const playerInfo = squadPlayersWithStats.find(p => p.pid === pid);
data = { name: playerInfo ? playerInfo.name : 'Unknown Player', matches: 0 };
}
const matchesText = data.matches > 0 ? i18n.avgStatsForMatches(data.matches) : '';
$('#popup-title').html(`${data.name} <br><small style="color: #6c757d;">${matchesText}</small>`);
if (data.matches > 0) {
displayColumns.forEach(key => {
const displayName = i18n.statMappings[key] || key;
if (sumAvgColumns.includes(key)) {
const sum = data[`${key}_sum`] || 0,
avg = data[`${key}_avg`] || '0.00';
tableBody.append(`<tr><td>${displayName} (Tot/Avg)</td><td><strong>${sum} / ${avg}</strong></td></tr>`);
} else if (data[key] !== undefined) {
const suffix = key.includes('%') ? ' %' : '';
tableBody.append(`<tr><td>${displayName}</td><td><strong>${data[key]}${suffix}</strong></td></tr>`);
}
});
} else {
tableBody.append(`<tr><td colspan="2">${i18n.noMatchesForCriteria}</td></tr>`);
}
}
async function showAllPlayersComparisonPopup() {
$('#player-comparison-popup').remove();
if (window.location.href.includes('?p=players')) { await updateUserPlayerList(); }
await populateSquadPlayers();
if (squadPlayersWithStats.length === 0) { alert(i18n.noDataFound); return; }
const rawData = JSON.parse(await GM_getValue('playerStatsRaw', '{}'));
// DEĞİŞİKLİK BAŞLANGICI: Kategorileri value ve text olarak grupla
const allMatches = Object.values(rawData).flatMap(p => p.matches || []);
const matchTypes = new Map([['all', i18n.all]]);
allMatches.forEach(m => {
if(m.matchTypeValue && !matchTypes.has(m.matchTypeValue)) {
matchTypes.set(m.matchTypeValue, m.matchType);
}
});
const sortedMatchTypes = Array.from(matchTypes.entries()).sort((a,b) => a[0] === 'all' ? -1 : (b[0] === 'all' ? 1 : a[1].localeCompare(b[1])));
const matchTypeOptions = sortedMatchTypes.map(([value, text]) => `<option value="${value}">${text}</option>`).join('');
// DEĞİŞİKLİK SONU
const ageLimits = [i18n.all, i18n.none, i18n.under18, i18n.under21, i18n.under23];
let tableHeaderHTML = `<th data-sort-type="text" data-tooltip="${i18n.player}">${i18n.player}</th><th data-sort-type="numeric" data-tooltip="${i18n.age}">${i18n.age}</th><th data-sort-type="numeric" data-tooltip="${i18n.matches}">${i18n.matches}</th>${displayColumns.map(col => `<th data-sort-type="numeric" data-tooltip="${i18n.statMappings[col] || col}">${col}</th>`).join('')}`;
const popupHTML = `
<div id="player-comparison-popup">
<div class="popup-header"><h3>${i18n.playerComparison}</h3><span class="popup-close">×</span></div>
<div class="popup-content-scrollable">
<div class="popup-filters">
<select id="compare-filter-type">${matchTypeOptions}</select>
<select id="compare-filter-age">${ageLimits.map(opt => `<option>${opt}</option>`).join('')}</select>
<button id="export-to-excel-btn" class="mz-script-button" style="background-color: #28a745; margin-left: auto;">${i18n.exportToExcel}</button>
</div>
<div class="table-responsive-wrapper">
<table id="comparison-table"><thead><tr>${tableHeaderHTML}</tr></thead><tbody></tbody></table>
</div>
</div>
</div>`;
$('body').append(popupHTML);
updateComparisonTable();
$('#player-comparison-popup .popup-close').on('click', () => $('#player-comparison-popup').remove());
$('#player-comparison-popup .popup-filters select').on('change', updateComparisonTable);
$('#export-to-excel-btn').on('click', handleExcelExport);
}
async function handleExcelExport() {
const button = $('#export-to-excel-btn');
const originalText = button.text();
button.text(i18n.exporting).prop('disabled', true);
try {
const wb = XLSX.utils.book_new();
const rawData = JSON.parse(await GM_getValue('playerStatsRaw', '{}'));
const filterAge = $('#compare-filter-age').val();
const matchTypes = new Map();
$('#compare-filter-type option').each(function() {
matchTypes.set($(this).val(), $(this).text());
});
for (const [filterTypeValue, filterTypeText] of matchTypes.entries()) {
const sheetData = await generateSheetData(rawData, filterTypeValue, filterAge);
if (sheetData.length > 0) {
const ws = XLSX.utils.json_to_sheet(sheetData);
const sheetName = filterTypeText.replace(/[\/\\?*\[\]]/g, '').substring(0, 31);
XLSX.utils.book_append_sheet(wb, ws, sheetName);
}
}
XLSX.writeFile(wb, 'ManagerZone_Player_Comparison.xlsx');
} catch (error) {
console.error("Excel export failed:", error);
alert(i18n.exportError);
} finally {
button.text(originalText).prop('disabled', false);
}
}
async function generateSheetData(rawData, filterTypeValue, filterAge) {
const dataForSheet = [];
for (const player of squadPlayersWithStats) {
const data = calculateAverages(rawData[player.pid], filterTypeValue, filterAge);
if (data && data.matches > 0) {
const playerRow = {};
playerRow[i18n.player] = player.name;
playerRow[i18n.age] = player.age > 0 ? player.age : '?';
playerRow[i18n.matches] = data.matches;
displayColumns.forEach(col => {
const displayName = i18n.statMappings[col] || col;
if (sumAvgColumns.includes(col)) {
const sum = data[`${col}_sum`] || 0;
const avg = data[`${col}_avg`] || '0.00';
playerRow[`${displayName} ${i18n.totalSuffix}`] = sum;
playerRow[`${displayName} ${i18n.avgSuffix}`] = parseFloat(avg);
} else {
const statValue = data[col] || '0.00';
playerRow[displayName] = parseFloat(statValue);
}
});
dataForSheet.push(playerRow);
}
}
return dataForSheet;
}
async function updateComparisonTable() {
const filterTypeValue = $('#compare-filter-type').val(),
filterAge = $('#compare-filter-age').val(),
rawData = JSON.parse(await GM_getValue('playerStatsRaw', '{}')),
tableBody = $('#comparison-table tbody').empty();
for (const player of squadPlayersWithStats) {
const data = calculateAverages(rawData[player.pid], filterTypeValue, filterAge);
if (data && data.matches > 0) {
const currentAge = player.age,
displayAge = currentAge > 0 ? currentAge : '?';
let row = `<tr data-pid="${player.pid}"><td>${player.name}</td><td data-sort="${currentAge}">${displayAge}</td><td data-sort="${data.matches}">${data.matches}</td>`;
displayColumns.forEach(col => {
if (sumAvgColumns.includes(col)) {
const sum = data[`${col}_sum`] || 0,
avg = data[`${col}_avg`] || '0.00';
row += `<td data-sort-sum="${sum}" data-sort-avg="${avg}">${sum} / ${avg}</td>`;
} else {
const statValue = data[col] || '0.00',
suffix = col.includes('%') ? ' %' : '';
row += `<td data-sort="${parseFloat(statValue)}">${statValue}${suffix}</td>`;
}
});
row += '</tr>';
tableBody.append(row);
}
}
makeTableSortable($('#comparison-table'));
applyCellColoring($('#comparison-table'));
}
function makeTableSortable(table) {
table.find('th[data-sort-type]').off('click').on('click', function() {
const th = $(this),
index = th.index(),
headerText = th.text().trim(),
isSpecialSort = sumAvgColumns.includes(headerText);
table.find('th').not(th).removeClass('sort-asc sort-desc sort-sum-asc sort-sum-desc sort-avg-asc sort-avg-desc').removeAttr('data-sort-state');
let sortKey, direction, sortAttribute;
if (isSpecialSort) {
const states = ['sum-desc', 'sum-asc', 'avg-desc', 'avg-asc'],
currentState = th.attr('data-sort-state') || '',
nextIndex = (states.indexOf(currentState) + 1) % states.length,
newState = states[nextIndex];
th.attr('data-sort-state', newState).removeClass('sort-asc sort-desc sort-sum-asc sort-sum-desc sort-avg-asc sort-avg-desc').addClass(`sort-${newState}`);
[sortKey, direction] = newState.split('-');
sortAttribute = `data-sort-${sortKey}`;
} else {
direction = th.hasClass('sort-asc') ? 'desc' : 'asc';
th.removeClass('sort-asc sort-desc').addClass(`sort-${direction}`);
sortAttribute = 'data-sort';
th.removeAttr('data-sort-state');
}
const rows = table.find('tbody tr').get();
rows.sort((a, b) => {
let valA, valB;
const tdA = $(a).find('td').eq(index),
tdB = $(b).find('td').eq(index);
if (isSpecialSort) {
valA = parseFloat(tdA.attr(sortAttribute)) || 0;
valB = parseFloat(tdB.attr(sortAttribute)) || 0;
} else {
valA = tdA.attr(sortAttribute) || 0;
valB = tdB.attr(sortAttribute) || 0;
if (isNaN(parseFloat(valA)) || isNaN(parseFloat(valB))) return direction === 'asc' ? valA.localeCompare(valB, lang === 'tr' ? 'tr-TR' : undefined) : valB.localeCompare(valA, lang === 'tr' ? 'tr-TR' : undefined);
valA = parseFloat(valA);
valB = parseFloat(valB);
}
return direction === 'asc' ? valA - valB : valB - valA;
});
$.each(rows, (idx, row) => table.children('tbody').append(row));
});
}
function applyCellColoring(table) {
const headers = table.find('thead th').map(function() {
return $(this).text();
}).get();
const goodStats = ['MP', 'G', 'A', 'S', 'G%', 'SOT', 'SOT%', 'P%', 'PL', 'In', 'T', 'T%', 'Pos%', 'Dist', 'DistP', 'SpR', 'SV'];
const badStats = ['SpP', 'YC', 'RC'];
headers.forEach((header, index) => {
if (goodStats.includes(header) || badStats.includes(header)) {
let values = [];
table.find(`tbody tr td:nth-child(${index + 1})`).each(function() {
let sortVal = sumAvgColumns.includes(header) ? $(this).attr('data-sort-avg') : $(this).attr('data-sort');
values.push(parseFloat(sortVal) || 0);
});
const min = Math.min(...values),
max = Math.max(...values);
if (min === max) return;
table.find(`tbody tr td:nth-child(${index + 1})`).each(function() {
let sortVal = sumAvgColumns.includes(header) ? $(this).attr('data-sort-avg') : $(this).attr('data-sort');
const value = parseFloat(sortVal) || 0,
normalized = max === min ? 0.5 : (value - min) / (max - min);
let hue = badStats.includes(header) ? 120 - (normalized * 120) : normalized * 120;
$(this).css('background-color', `hsla(${hue}, 70%, 75%, 0.6)`).addClass('colored-cell');
});
}
});
}
function setupTooltipListeners() {
const tooltip = $('#custom-tooltip');
let currentTarget = null;
$(document).on('click', '[data-tooltip]', function(e) {
e.stopPropagation();
const target = $(this);
const tooltipText = target.attr('data-tooltip');
if (currentTarget && currentTarget[0] === target[0]) {
tooltip.hide();
currentTarget = null;
return;
}
currentTarget = target;
if (tooltipText) {
tooltip.html(tooltipText).show();
const targetOffset = target.offset(),
targetHeight = target.outerHeight(),
targetWidth = target.outerWidth(),
tooltipHeight = tooltip.outerHeight(),
tooltipWidth = tooltip.outerWidth();
let top = targetOffset.top - tooltipHeight - 10;
let left = targetOffset.left + (targetWidth / 2) - (tooltipWidth / 2);
if (top < 0) top = targetOffset.top + targetHeight + 10;
if (left < 0) left = 5;
if (left + tooltipWidth > $(window).width()) left = $(window).width() - tooltipWidth - 5;
tooltip.css({
top: top,
left: left
});
}
});
$(document).on('click', function(e) {
if (currentTarget && !$(e.target).is(currentTarget)) {
tooltip.hide();
currentTarget = null;
}
});
$(document).on('mouseenter', '[data-tooltip]', function() {
if ('ontouchstart' in window) return; // Don't show on hover for touch devices
const target = $(this);
const tooltipText = target.attr('data-tooltip');
if (tooltipText) {
tooltip.html(tooltipText).show();
const targetOffset = target.offset(),
targetHeight = target.outerHeight(),
targetWidth = target.outerWidth(),
tooltipHeight = tooltip.outerHeight(),
tooltipWidth = tooltip.outerWidth();
let top = targetOffset.top - tooltipHeight - 10;
let left = targetOffset.left + (targetWidth / 2) - (tooltipWidth / 2);
if (top < 0) top = targetOffset.top + targetHeight + 10;
if (left < 0) left = 5;
if (left + tooltipWidth > $(window).width()) left = $(window).width() - tooltipWidth - 5;
tooltip.css({
top: top,
left: left
});
}
}).on('mouseleave', '[data-tooltip]', function() {
if ('ontouchstart' in window) return; // Don't hide on leave for touch devices if it was opened by click
if (!currentTarget) { // Only hide if it wasn't "click-locked"
tooltip.hide();
}
});
}
}
/****************************************************************************************
* *
* BÖLÜM 3: PLAY-OFF/PLAY-OUT TAHMİNCİSİ (Evrensel) *
* *
****************************************************************************************/
function initializePlayoffPredictorScript() {
let isJobRunning = false;
function parseLeagueName(name) {
const lowerName = name.toLowerCase().trim();
const match = lowerName.match(/div(\d+)\.(\d+)/);
if (match) {
return {
level: parseInt(match[1], 10),
index: parseInt(match[2], 10),
isTopLeague: false
};
} else if (lowerName && !lowerName.startsWith('div')) {
return {
level: 0,
index: 1,
isTopLeague: true
};
}
return null;
}
function generateRules(level, index, currentLeagueName, isTopLeague) {
const generatedRules = [];
if (!isTopLeague) {
const parentLevel = level - 1;
const parentIndex = Math.floor((index - 1) / 3) + 1;
const leagueSelect = getLeagueSelect();
let parentLeagueName = `div${parentLevel}.${parentIndex}`;
if (parentLevel === 0) {
for (const option of leagueSelect.options) {
const leagueInfo = parseLeagueName(option.text);
if (leagueInfo && leagueInfo.isTopLeague) {
parentLeagueName = option.text;
break;
}
}
}
const firstSiblingIndex = (parentIndex - 1) * 3 + 1;
const childLeague1 = `div${level}.${firstSiblingIndex}`;
const childLeague2 = `div${level}.${firstSiblingIndex + 1}`;
const childLeague3 = `div${level}.${firstSiblingIndex + 2}`;
const groupBaseNum = parentIndex * 2;
generatedRules.push({
name: `Play-Off Grup ${groupBaseNum - 1} (Yükselme)`,
type: 'playoff',
teams: [
{ league: parentLeagueName, rank: 8 },
{ league: childLeague1, rank: 2 },
{ league: childLeague2, rank: 3 },
{ league: childLeague3, rank: 3 }
]
});
generatedRules.push({
name: `Play-Off Grup ${groupBaseNum} (Yükselme)`,
type: 'playoff',
teams: [
{ league: parentLeagueName, rank: 9 },
{ league: childLeague1, rank: 3 },
{ league: childLeague2, rank: 2 },
{ league: childLeague3, rank: 2 }
]
});
}
if (level < 6) {
const nextLevel = level + 1;
const childBaseIndex = (index - 1) * 3;
const childLeague1 = `div${nextLevel}.${childBaseIndex + 1}`;
const childLeague2 = `div${nextLevel}.${childBaseIndex + 2}`;
const childLeague3 = `div${nextLevel}.${childBaseIndex + 3}`;
const groupNameSuffix = isTopLeague ? "(Yükselme/Düşme)" : "(Düşme)";
const groupBaseIndex = isTopLeague ? 0 : (((3 ** (level - 1) - 1) / 2) + index - 1);
const groupNum1 = (groupBaseIndex * 2) + 1;
const groupNum2 = (groupBaseIndex * 2) + 2;
generatedRules.push({
name: `Play-Out Grup ${isTopLeague ? 'A' : groupNum1} ${groupNameSuffix}`,
type: 'playout',
teams: [
{ league: currentLeagueName, rank: 8 },
{ league: childLeague1, rank: 2 },
{ league: childLeague2, rank: 3 },
{ league: childLeague3, rank: 3 }
]
});
generatedRules.push({
name: `Play-Out Grup ${isTopLeague ? 'B' : groupNum2} ${groupNameSuffix}`,
type: 'playout',
teams: [
{ league: currentLeagueName, rank: 9 },
{ league: childLeague1, rank: 3 },
{ league: childLeague2, rank: 2 },
{ league: childLeague3, rank: 2 }
]
});
}
const uniqueRules = [];
const seenNames = new Set();
for (const rule of generatedRules) {
if (!seenNames.has(rule.name)) {
uniqueRules.push(rule);
seenNames.add(rule.name);
}
}
if (isTopLeague) {
return uniqueRules.filter(rule => rule.type === 'playout');
}
return uniqueRules;
}
function findRelevantRules(currentLeagueName) {
const leagueInfo = parseLeagueName(currentLeagueName);
if (!leagueInfo) {
console.warn("Mevcut lig ayrıştırılamadı:", currentLeagueName);
return [];
}
if (leagueInfo.level >= 7) {
return [];
}
return generateRules(leagueInfo.level, leagueInfo.index, currentLeagueName, leagueInfo.isTopLeague);
}
async function startJob() {
if (isJobRunning) {
alert("Zaten devam eden bir işlem var. Lütfen tamamlanmasını bekleyin.");
return;
}
isJobRunning = true;
showProgressIndicator("İşlem Başlatılıyor...");
const leagueSelect = getLeagueSelect();
if (!leagueSelect) {
alert("Lig seçme menüsü bulunamadı!");
isJobRunning = false;
removeProgressIndicator();
return;
}
const currentLeagueName = leagueSelect.options[leagueSelect.selectedIndex].text;
const relevantRules = findRelevantRules(currentLeagueName);
if (!relevantRules || relevantRules.length === 0) {
let predictorContainer = document.getElementById('playoff-predictor-container');
if (!predictorContainer) {
const mainContentArea = document.querySelector('#league_navigation > div.ui-tabs-panel');
predictorContainer = setupContainer(mainContentArea);
}
predictorContainer.innerHTML = `<div style="padding: 15px; background-color: #f8f9fa; border: 1px solid #dee2e6; text-align: center; margin-top: 20px;">Bu lig (${currentLeagueName}) için gösterilecek Play-Off/Out kuralı bulunmuyor.</div>`;
isJobRunning = false;
removeProgressIndicator();
return;
}
const allTeamDefs = getUniqueTeamDefs(relevantRules);
const leagueIdMap = createLeagueIdMap(leagueSelect);
const collectedData = [];
let hiddenIframe = null;
try {
hiddenIframe = document.createElement('iframe');
hiddenIframe.style.cssText = 'display: none !important;';
document.body.appendChild(hiddenIframe);
const teamDefsToFetchInIframe = allTeamDefs.filter(def => def.league.toLowerCase() !== currentLeagueName.toLowerCase());
const teamDefsOnCurrentPage = allTeamDefs.filter(def => def.league.toLowerCase() === currentLeagueName.toLowerCase());
teamDefsOnCurrentPage.forEach(def => {
const teamData = parseTeamFromDocument(document, def.rank, window.location.href);
if (teamData) collectedData.push({ ...teamData,
league: def.league
});
});
let leaguesProcessed = teamDefsOnCurrentPage.length;
for (const teamDef of teamDefsToFetchInIframe) {
if (!isJobRunning) {
throw new Error("İşlem kullanıcı tarafından iptal edildi.");
}
updateProgressIndicator(`Lig Verileri Toplanıyor... (${leaguesProcessed}/${allTeamDefs.length}) - ${teamDef.league}`);
const teamData = await fetchLeagueDataViaIframe(teamDef, leagueIdMap, hiddenIframe);
collectedData.push(teamData);
leaguesProcessed++;
}
updateProgressIndicator("Lig verileri toplandı. Takım değerleri hesaplanıyor...");
let teamsWithValueProcessed = 0;
const validTeams = collectedData.filter(d => d && d.url && d.url !== '#');
const totalTeamsWithValue = validTeams.length;
const valuePromises = validTeams.map(team => {
if (!isJobRunning) return Promise.reject(new Error("İşlem iptal edildi."));
return fetchTop11Value(team).then(result => {
teamsWithValueProcessed++;
updateProgressIndicator(`En İyi 11 Değerleri Hesaplanıyor... (${teamsWithValueProcessed}/${totalTeamsWithValue})`);
return result;
});
});
const finalDataWithValues = await Promise.all(valuePromises);
const finalData = collectedData.map(team => {
const teamWithValue = finalDataWithValues.find(t => t && team && t.url === team.url);
return teamWithValue || team;
});
if (!isJobRunning) return;
updateProgressIndicator("Tablo Oluşturuluyor...");
const mainContentArea = document.querySelector('#league_navigation > div.ui-tabs-panel');
let predictorContainer = setupContainer(mainContentArea);
const finalHtml = createTablesHTML(relevantRules, finalData);
predictorContainer.innerHTML = finalHtml;
} catch (error) {
if (error.message.includes("iptal edildi")) {
console.log("İşlem durduruldu.");
} else {
console.error("Betiğin çalışması sırasında bir hata oluştu:", error);
alert("Betiğin çalışması sırasında bir hata oluştu. Lütfen konsolu kontrol edin.");
}
} finally {
if (hiddenIframe) hiddenIframe.remove();
removeProgressIndicator();
isJobRunning = false;
}
}
function fetchLeagueDataViaIframe(teamDef, leagueIdMap, iframe) {
return new Promise((resolve) => {
const leagueNameLower = teamDef.league.toLowerCase();
const sid = leagueIdMap[leagueNameLower];
if (!sid) {
console.warn(`Lig ID bulunamadı: ${teamDef.league}`);
resolve({
rank: teamDef.rank,
league: teamDef.league,
name: `(Lig bulunamadı)`,
url: '#'
});
return;
}
const url = `https://www.managerzone.com/?p=league&type=senior&sid=${sid}`;
let poller = null;
let timeout = null;
const cleanup = () => {
if (poller) clearInterval(poller);
if (timeout) clearTimeout(timeout);
iframe.onload = null;
iframe.onerror = null;
};
const fail = (errorType) => {
console.warn(`${errorType} for league: ${teamDef.league}`);
cleanup();
resolve({
rank: teamDef.rank,
league: teamDef.league,
name: `(${errorType})`,
url: '#'
});
};
timeout = setTimeout(() => fail('Timeout Hatası'), 15000);
iframe.onload = () => {
poller = setInterval(() => {
try {
const doc = iframe.contentDocument;
if (doc && doc.querySelector('.nice_table tbody tr')) {
cleanup();
const teamData = parseTeamFromDocument(doc, teamDef.rank, url);
resolve({ ...teamData,
league: teamDef.league
});
}
} catch (e) {
// ignore error
}
}, 300);
};
iframe.onerror = () => fail('Yükleme Hatası');
iframe.src = url;
});
}
function fetchTop11Value(team) {
if (!team || !team.url) return Promise.resolve({ ...team,
top11Value: 'URL Yok'
});
const teamId = new URL(team.url).searchParams.get('tid');
if (!teamId) return Promise.resolve({ ...team,
top11Value: 'ID Yok'
});
const playersUrl = `https://www.managerzone.com/?p=players&sub=alt&tid=${teamId}`;
return fetch(playersUrl)
.then(response => {
if (!response.ok) throw new Error(`HTTP hatası! Durum: ${response.status}`);
return response.text();
})
.then(html => ({ ...team,
top11Value: calculateTop11ValueFromHTML(html)
}))
.catch(error => {
console.error(`Takım ID ${teamId} için "En İyi 11" değeri alınamadı:`, error);
return { ...team,
top11Value: 'Hata'
};
});
}
function waitForElement(selector, callback) {
const i = setInterval(() => {
const e = document.querySelector(selector);
if (e) {
clearInterval(i);
callback();
}
}, 100);
}
function createLeagueIdMap(leagueSelect) {
const map = {};
for (const o of leagueSelect.options) {
map[o.text.toLowerCase().trim()] = o.value;
}
return map;
}
function setupContainer(parent) {
let c = document.getElementById('playoff-predictor-container');
if (c) {
c.innerHTML = '';
} else {
c = document.createElement('div');
c.id = 'playoff-predictor-container';
const t = parent.querySelector('.nice_table');
if (t) t.parentNode.insertAdjacentElement('afterend', c);
else parent.appendChild(c);
}
return c;
}
function getLeagueSelect() {
const s = document.querySelectorAll('select[onchange*="p=league&type=senior&sid="]');
return s.length > 1 ? s[1] : s[0];
}
function getUniqueTeamDefs(rules) {
const u = [];
const seen = new Set();
rules.forEach(g => {
g.teams.forEach(t => {
const key = `${t.league}-${t.rank}`;
if (!seen.has(key)) {
u.push(t);
seen.add(key);
}
});
});
return u;
}
function parseTeamFromDocument(doc, rank, sourceUrl = 'Bilinmiyor') {
if (!doc) {
console.warn(`Belge (document) yok. Sıra: ${rank}, Kaynak: ${sourceUrl}`);
return {
rank,
name: `(Belge Okunamadı)`,
url: '#'
};
}
const tr = [...doc.querySelectorAll('.nice_table tbody tr')].find(r => r.cells[0]?.textContent.trim() == rank);
if (tr && tr.cells[1]) {
const a = tr.cells[1].querySelector('a[href*="p=team&tid="]');
if (a) {
const stats = {
g: tr.cells[3]?.textContent.trim(),
b: tr.cells[4]?.textContent.trim(),
m: tr.cells[5]?.textContent.trim(),
a: tr.cells[6]?.textContent.trim(),
y: tr.cells[7]?.textContent.trim(),
av: tr.cells[8]?.textContent.trim(),
p: tr.cells[9]?.textContent.trim()
};
return {
rank,
name: a.textContent.trim(),
url: a.href,
...stats
};
}
}
console.warn(`Takım bulunamadı. Sıra: ${rank}, Kaynak: ${sourceUrl}`);
return {
rank,
name: `(Sıra ${rank} bulunamadı)`,
url: '#'
};
}
// DEĞİŞİKLİK BAŞLANGICI: Evrensel Değer Sütunu Tespiti
function calculateTop11ValueFromHTML(htmlText) {
try {
const doc = new DOMParser().parseFromString(htmlText, 'text/html');
const playerTable = doc.getElementById('playerAltViewTable');
if (!playerTable) return 'Tablo Yok';
const firstDataRow = playerTable.querySelector('tbody tr');
if (!firstDataRow) return 'Oyuncu Yok';
let valueColumnIndex = -1;
const headers = playerTable.querySelectorAll('thead th');
const firstDataRowCells = firstDataRow.cells;
// 1. Strateji: Başlıklarda "Değer" veya benzeri bir kelime ara (Regex ile evrensel)
if (headers.length > 0) {
for (let i = 0; i < headers.length; i++) {
if (/value|değer|wert|valore|valor/i.test(headers[i].textContent)) {
valueColumnIndex = i;
break;
}
}
}
// 2. Strateji: Başlık bulunamazsa, ilk veri satırında para birimi ara
if (valueColumnIndex === -1 && firstDataRowCells) {
for (let i = 0; i < firstDataRowCells.length; i++) {
if (/\s(EUR|USD|TRY)/.test(firstDataRowCells[i].textContent)) {
valueColumnIndex = i;
break;
}
}
}
if (valueColumnIndex === -1) {
console.warn("Değer sütunu bulunamadı.");
return 'Sütun Yok';
}
const values = [];
playerTable.querySelectorAll('tbody tr').forEach(row => {
const cell = row.cells[valueColumnIndex];
if (cell) {
const numericValue = parseInt(cell.textContent.trim().replace(/[^0-9]/g, ''), 10);
if (!isNaN(numericValue)) {
values.push(numericValue);
}
}
});
if (values.length === 0) return 'Değer Yok';
values.sort((a, b) => b - a);
const sum = values.slice(0, 11).reduce((acc, val) => acc + val, 0);
return `${sum.toLocaleString('de-DE')} EUR`;
} catch (e) {
console.error("HTML parse/hesaplama hatası:", e);
return "Hesaplama Hatası";
}
}
// DEĞİŞİKLİK SONU
function createTablesHTML(rules, data) {
const playoffRules = rules.filter(r => r.type === 'playoff');
const playoutRules = rules.filter(r => r.type === 'playout');
let h = `<h2 class="subheader clearfix" style="margin-top: 20px;">Potansiyel Play-Off / Play-Out Grupları</h2><div style="font-size: 0.9em; padding: 5px; background-color: #fff3cd; border: 1px solid #ffeeba; border-radius: 4px; margin-bottom: 10px; color: #856404;"><strong>Not:</strong> Bu tablolar, anlık puan durumlarına göre oluşturulmuş bir <strong>tahmindir</strong> ve resmi değildir.</div>`;
const generateHtmlForRules = (ruleSet) => {
let partialHtml = '';
ruleSet.forEach(g => {
partialHtml += `<h3 class="subheader clearfix">${g.name}</h3><div class="mainContent" style="padding: 5px; margin-bottom: 20px;"><table class="nice_table" style="width:100%;">
<thead><tr><th style="width:15%;">Lig</th><th style="width:5%; text-align: center;">Sıra</th><th style="width:20%;">Potansiyel Takım</th><th title="Galibiyet" style="width:4%; text-align: center;">G</th><th title="Beraberlik" style="width:4%; text-align: center;">B</th><th title="Mağlubiyet" style="width:4%; text-align: center;">M</th><th title="Attığı Gol" style="width:4%; text-align: center;">A</th><th title="Yediği Gol" style="width:4%; text-align: center;">Y</th><th title="Averaj" style="width:4%; text-align: center;">Av</th><th title="Puan" style="width:5%; text-align: center;">P</th><th style="width:15%; text-align: center;">En Değerli 11</th></tr></thead><tbody>`;
g.teams.forEach(t => {
const d = data.find(ad => ad && ad.league && t.league && ad.league.toLowerCase() === t.league.toLowerCase() && ad.rank === t.rank);
let teamCell = `...`;
let statsCells = '<td colspan="7" style="text-align:center;">- Veri Yok -</td>';
let top11Cell = '<td>...</td>';
let rankCell = `<td style="text-align: center;">${t.rank}</td>`;
if (d) {
teamCell = d.url && d.url !== '#' ? `<a href="${d.url}" target="_blank" title="${d.name}">${d.name}</a>` : d.name;
if (d.p !== undefined) statsCells = `<td style="text-align: center;">${d.g || '-'}</td><td style="text-align: center;">${d.b || '-'}</td><td style="text-align: center;">${d.m || '-'}</td><td style="text-align: center;">${d.a || '-'}</td><td style="text-align: center;">${d.y || '-'}</td><td style="text-align: center;">${d.av || '-'}</td><td style="text-align: center;"><b>${d.p}</b></td>`;
top11Cell = `<td style="text-align: center;">${d.top11Value || 'Hesaplanıyor...'}</td>`;
} else {
teamCell = `<span style="color:red;">(Veri alınamadı)</span>`;
}
partialHtml += `<tr><td>${t.league}</td>${rankCell}<td>${teamCell}</td>${statsCells}${top11Cell}</tr>`;
});
partialHtml += `</tbody></table></div>`;
});
return partialHtml;
};
if (playoffRules.length > 0) {
h += generateHtmlForRules(playoffRules);
}
if (playoutRules.length > 0) {
h += generateHtmlForRules(playoutRules);
}
h += `<hr><div style="font-size:0.9em; padding:10px; background-color: #f0f0f0; border: 1px solid #ddd; border-radius: 4px;"><h4>Bilgi</h4><p>Yukarıdaki puan tablosunun kesin olmadığını, takım pozisyonlarının çekişmeli olması durumunda play-off/out gruplarının değişebileceğini unutmayın. Olası eşitlik durumlarında sıralamaların hesaplanması için aşağıdaki öncelik sırasına göre bir dizi kural uygulanır:</p><ul><li><strong>Puanı en yüksek olan takım:</strong> En temel sıralama ölçütüdür.</li><li><strong>Averaj:</strong> Puan eşitliğinde averajı daha iyi olan takım üstte yer alır.</li><li><strong>Sezon içi galibiyet sayısı:</strong> Puan ve averaj eşitliğinde daha fazla galibiyeti olan takım önceliklidir.</li><li><strong>Gol sayısı:</strong> Yukarıdaki tüm kriterler eşitse, daha fazla gol atan takım üstte yer alır.</li><li><strong>Kura çekimi:</strong> Tüm istatistiklerin tamamen aynı olması gibi çok nadir bir durumda sıralama kura ile belirlenir.</li></ul></div>`;
return h;
}
function showProgressIndicator(message) {
removeProgressIndicator();
const i = document.createElement('div');
i.id = 'mz-predictor-progress';
i.style = "position: fixed; top: 0; left: 0; width: 100%; background: #00529B; color: white; padding: 10px; z-index: 9999; text-align: center; font-size: 1.1em; border-bottom: 2px solid #003666; box-shadow: 0 2px 5px rgba(0,0,0,0.5);";
i.innerHTML = `<span><i class="fa fa-spinner fa-pulse"></i> ${message}</span> <button id="cancelJobBtn" style="margin-left: 20px; background: #c00; color: white; border: 1px solid white; border-radius: 4px; cursor: pointer; padding: 2px 8px;">Durdur</button>`;
document.body.appendChild(i);
document.getElementById('cancelJobBtn').onclick = () => {
isJobRunning = false;
};
}
function updateProgressIndicator(message) {
const i = document.getElementById('mz-predictor-progress');
if (i) i.querySelector('span').innerHTML = `<i class="fa fa-spinner fa-pulse"></i> ${message}`;
}
function removeProgressIndicator() {
const i = document.getElementById('mz-predictor-progress');
if (i) i.remove();
}
function initButton() {
const container = document.querySelector('div[style*="float: right; line-height: 36px;"]');
if (container && !document.getElementById('runPredictorBtn')) {
const btn = document.createElement('a');
btn.href = '#';
btn.className = 'mzbtn buttondiv button_account';
btn.id = 'runPredictorBtn';
btn.style.marginLeft = '10px';
btn.innerHTML = `<span class="buttonClassMiddle"><span style="white-space: nowrap;"><i class="fa fa-sitemap" aria-hidden="true"></i> Play-Off/Out Tahmin Et</span></span><span class="buttonClassRight"> </span>`;
btn.addEventListener('click', (e) => {
e.preventDefault();
startJob();
});
container.appendChild(btn);
}
}
waitForElement('div[style*="float: right; line-height: 36px;"]', initButton);
}
/****************************************************************************************
* *
* BÖLÜM 4: TAKİP LİSTESİ FİLTRELEME (Shortlist Filter) - DÜZELTİLDİ v4 (SON VERSİYON) *
* *
****************************************************************************************/
function initializeShortlistFilterScript() {
// Değişken adını daha anlaşılır hale getiriyoruz
let userCountryCode = null;
// FİLTRE HATASI DÜZELTİLDİ: Ülke tespiti için en güvenilir yöntem olan bayrak resmindeki 2 harfli ülke kodu kullanılıyor.
async function fetchAndSetUserCountryCode() {
if (userCountryCode !== null) return userCountryCode;
try {
const cachedCode = await GM_getValue('user_country_code');
if (cachedCode) {
userCountryCode = cachedCode;
console.log('Kullanıcı ülke kodu önbellekten alındı:', userCountryCode);
return userCountryCode;
}
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: "https://www.managerzone.com/?p=team", // Takım sayfası en güvenilir bilgiyi içeriyor
onload: resolve,
onerror: reject
});
});
const html = response.responseText;
const doc = new DOMParser().parseFromString(html, 'text/html');
const countryImg = doc.querySelector('#infoAboutTeam img[src*="img/flags/s_"]');
if (countryImg) {
const src = countryImg.getAttribute('src');
const match = src.match(/s_([a-z]{2})\.gif/); // Resim adından 2 harfli kodu alır (örn: "tr")
if (match && match[1]) {
userCountryCode = match[1];
await GM_setValue('user_country_code', userCountryCode);
console.log('Kullanıcı ülke kodu tespit edildi:', userCountryCode);
} else {
console.error('Bayrak resminden ülke kodu ayıklanamadı.');
userCountryCode = '';
}
} else {
console.error('Kullanıcı ülkesi Takım sayfasından tespit edilemedi.');
userCountryCode = '';
}
} catch (error) {
console.error("Kullanıcı ülke kodu alınırken hata oluştu:", error);
userCountryCode = '';
}
return userCountryCode;
}
function addFilterUI() {
GM_addStyle(`
.sl-action-btn { padding: 5px 12px !important; cursor: pointer; color: white !important; border: 1px solid rgba(0,0,0,0.2) !important; border-radius: 4px !important; font-weight: bold !important; font-size: 13px !important; text-decoration: none !important; display: inline-block !important; line-height: normal !important; height: auto !important; }
#shortlist-filter-btn { background-color: #4CAF50; } #shortlist-filter-btn:hover { background-color: #45a049; }
#script-controls-wrapper { display: flex; gap: 10px; align-items: center; margin-bottom: 10px; padding: 10px; background: #f1f1f1; border: 1px solid #ddd; border-radius: 5px; }
#shortlist-filter-controls { display: flex; align-items: center; gap: 5px; }
#shortlist-filter-count { font-weight: bold; color: #00529B; }
.sl-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6); z-index: 10010; display: none; justify-content: center; align-items: center; }
.sl-modal-content { background: #fefefe; padding: 20px; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); min-width: 300px; display: flex; flex-direction: column; max-width: 450px; }
.sl-modal-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #ddd; padding-bottom: 10px; margin-bottom: 15px; }
.sl-modal-header h3 { margin: 0; font-size: 18px; } .sl-modal-close { font-size: 24px; font-weight: bold; cursor: pointer; color: #888; }
.sl-modal-body { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
.sl-filter-group { border: 1px solid #ddd; padding: 10px; border-radius: 5px; background: #fafafa; }
.sl-filter-group h4 { margin-top: 0; margin-bottom: 10px; border-bottom: 1px solid #eee; padding-bottom: 5px; }
.sl-filter-group label { display: block; margin-bottom: 5px; } .sl-age-inputs { display: flex; align-items: center; gap: 5px; }
.sl-modal-content input[type="number"] { width: 60px; padding: 5px; border: 1px solid #ccc; border-radius: 3px; }
.sl-modal-footer { border-top: 1px solid #ddd; padding-top: 15px; margin-top: 20px; text-align: center; }
.sl-modal-footer button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; color: white; margin: 0 5px; }
#shortlist-filter-apply { background-color: #007bff; } #shortlist-filter-reset { background-color: #6c757d; }
`);
const modalHTML = `
<div id="shortlist-filter-modal" class="sl-modal-overlay"> <div class="sl-modal-content"> <div class="sl-modal-header"> <h3>Oyuncu Filtrele</h3> <span class="sl-modal-close">×</span> </div> <div class="sl-modal-body"> <div class="sl-filter-group"> <h4>Yaş Aralığı</h4> <div class="sl-age-inputs"> <input type="number" id="min-age" placeholder="Min" min="15" max="40"> <span>-</span> <input type="number" id="max-age" placeholder="Max" min="15" max="40"> </div> </div> <div class="sl-filter-group"> <h4>Diğer</h4> <label> <input type="checkbox" id="retiring-only"> Emekli Olacaklar </label> </div> <div class="sl-filter-group" style="grid-column: 1 / -1;"> <h4>Milliyet</h4> <label><input type="radio" name="nationality" value="all" checked> Tümü</label> <label><input type="radio" name="nationality" value="national"> Yerli</label> <label><input type="radio" name="nationality" value="foreign"> Yabancı</label> </div> </div> <div class="sl-modal-footer"> <button id="shortlist-filter-apply">Filtrele</button> <button id="shortlist-filter-reset">Sıfırla</button> </div> </div> </div>
`;
$('body').append(modalHTML);
const playersListContainer = $('#players_container');
if (playersListContainer.length > 0 && $('#script-controls-wrapper').length === 0) {
const scriptWrapper = $(`<div id="script-controls-wrapper"><div id="shortlist-filter-controls"><button id="shortlist-filter-btn" class="sl-action-btn">Filtrele</button><span id="shortlist-filter-count"></span></div></div>`);
playersListContainer.before(scriptWrapper);
}
$('#shortlist-filter-btn').on('click', () => $('#shortlist-filter-modal').css('display', 'flex'));
$('#shortlist-filter-modal .sl-modal-close').on('click', () => $('#shortlist-filter-modal').hide());
$('#shortlist-filter-apply').on('click', applyFilters);
$('#shortlist-filter-reset').on('click', resetFilters);
$('#shortlist-filter-modal input[type="number"]').on('keydown keyup keypress', event => event.stopPropagation());
}
// FİLTRE HATASI DÜZELTİLDİ: Artık metin yerine bayrak resmindeki ülke kodları karşılaştırılıyor.
function applyFilters() {
const minAge = parseInt($('#min-age').val(), 10) || 0;
const maxAge = parseInt($('#max-age').val(), 10) || 100;
const retiringOnly = $('#retiring-only').is(':checked');
const nationalityFilter = $('input[name="nationality"]:checked').val();
let visibleCount = 0;
$('.playerContainer').each(function() {
const player = $(this);
let show = true;
const infoCells = player.find('.dg_playerview_info table td');
let age = -1;
infoCells.each(function(index) {
if (age === -1 && /yaş:|age:|alter:|edad:/i.test($(this).text().trim())) {
const ageText = infoCells.eq(index + 1).text();
age = parseInt(ageText.match(/\d+/)?.[0], 10);
}
});
if (age !== -1 && (age < minAge || age > maxAge)) {
show = false;
}
if (show) {
const isRetiring = player.find('.dg_playerview_retire').length > 0;
if (retiringOnly && !isRetiring) {
show = false;
}
}
if (show && userCountryCode && nationalityFilter !== 'all') {
const playerFlagImg = player.find('img[src*="img/flags/s_"]');
let playerCountryCode = null;
if (playerFlagImg.length > 0) {
const src = playerFlagImg.attr('src');
const match = src.match(/s_([a-z]{2})\.gif/);
if (match && match[1]) {
playerCountryCode = match[1];
}
}
if (playerCountryCode) {
if (nationalityFilter === 'national' && playerCountryCode !== userCountryCode) {
show = false;
}
if (nationalityFilter === 'foreign' && playerCountryCode === userCountryCode) {
show = false;
}
}
}
player.toggle(show);
if (show) {
visibleCount++;
}
});
const isFiltered = minAge > 0 || maxAge < 100 || retiringOnly || nationalityFilter !== 'all';
$('#shortlist-filter-count').text(isFiltered ? `${visibleCount} oyuncu filtrelendi.` : '');
$('#shortlist-filter-modal').hide();
}
function resetFilters() {
$('#min-age').val('');
$('#max-age').val('');
$('#retiring-only').prop('checked', false);
$('input[name="nationality"][value="all"]').prop('checked', true);
$('.playerContainer').show();
$('#shortlist-filter-count').text('');
$('#shortlist-filter-modal').hide();
}
function init() {
if ($('#players_container').length) {
fetchAndSetUserCountryCode();
addFilterUI();
}
}
init();
}
/****************************************************************************************
* *
* BÖLÜM 5: TRANSFER TAKİPÇİSİ (Transfer Tracker) - YENİ MODÜL *
* *
****************************************************************************************/
function initializeTransferTrackerScript() {
const i18nData = {
tr: {
trackPlayer: "Oyuncunun Transfer Geçmişini Takip Et",
historyFor: "için Transfer Geçmişi",
checking: "Kontrol ediliyor...",
noHistory: "Bu oyuncu için kayıtlı transfer geçmişi yok.",
clearHistory: "Geçmişi Temizle",
confirmClear: "Bu oyuncunun tüm transfer geçmişi silinecek. Emin misiniz?",
date: "Tarih",
status: "Durum",
price: "Fiyat",
bid: "Teklif",
deadline: "Bitiş",
buyer: "Alıcı",
statusListed: "Listelendi",
statusSold: "Satıldı",
statusExpired: "Satılmadı",
statusEnded: "Bitti (Kontrol bekleniyor)",
statusBid: "Teklif Var",
checkingShortlist: "Takip listesi transferleri kontrol ediliyor...",
checkComplete: "Transfer kontrolü tamamlandı.",
scanShortlistBtn: "Takip Listesini Tara",
},
en: {
trackPlayer: "Track Player's Transfer History",
historyFor: "Transfer History for",
checking: "Checking...",
noHistory: "No transfer history recorded for this player.",
clearHistory: "Clear History",
confirmClear: "Are you sure you want to delete all transfer history for this player?",
date: "Date",
status: "Status",
price: "Price",
bid: "Bid",
deadline: "Deadline",
buyer: "Buyer",
statusListed: "Listed",
statusSold: "Sold",
statusExpired: "Expired",
statusEnded: "Ended (Awaiting check)",
statusBid: "Bid Placed",
checkingShortlist: "Checking shortlist transfers...",
checkComplete: "Transfer check complete.",
scanShortlistBtn: "Scan Transfers",
}
};
const lang = ($('meta[name="language"]').attr('content') || 'en') === 'tr' ? 'tr' : 'en';
const getText = (key) => i18nData[lang][key];
const TRANSFER_HISTORY_KEY = 'mz_transfer_history_v2';
GM_addStyle(`
.transfer-track-btn {
padding: 0px 7px; margin-left: 8px; font-weight: bold; cursor: pointer;
background-color: #17a2b8; color: white; border: 1px solid #107686;
border-radius: 4px; font-size: 12px; vertical-align: middle;
}
.tt-modal-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background-color: rgba(0,0,0,0.6); z-index: 10020; display: none;
justify-content: center; align-items: center;
}
.tt-modal-content {
background: #f8f9fa; padding: 20px; border-radius: 8px;
box-shadow: 0 5px 15px rgba(0,0,0,0.3); width: 90%; max-width: 850px;
max-height: 90vh; display: flex; flex-direction: column;
}
.tt-modal-header {
display: flex; justify-content: space-between; align-items: center;
border-bottom: 1px solid #dee2e6; padding-bottom: 10px; margin-bottom: 15px;
}
.tt-modal-header h3 { margin: 0; font-size: 18px; color: #00529B;}
.tt-modal-close { font-size: 24px; font-weight: bold; cursor: pointer; color: #888; }
.tt-modal-body {
overflow-y: auto;
flex-grow: 1;
}
.tt-tab-container {
display: flex;
border-bottom: 1px solid #dee2e6;
margin-bottom: 15px;
}
.tt-tab {
padding: 8px 16px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
margin-bottom: -1px;
font-weight: bold;
color: #00529B;
}
.tt-tab.active {
background-color: #fff;
border-color: #dee2e6 #dee2e6 #fff;
border-radius: 4px 4px 0 0;
}
.tt-tab-content {
display: none;
}
.tt-tab-content.active {
display: block;
}
#tt-chart-container {
width: 100%;
height: 400px;
}
.tt-history-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.tt-history-table th, .tt-history-table td { border: 1px solid #dee2e6; padding: 8px; text-align: left; }
.tt-history-table th { background-color: #e9ecef; }
.tt-history-table .status-listed { color: #007bff; }
.tt-history-table .status-bid { color: #ff8c00; }
.tt-history-table .status-sold { color: #28a745; font-weight: bold; }
.tt-history-table .status-expired { color: #dc3545; }
.tt-modal-footer { margin-top: 15px; text-align: right; }
`);
async function showTransferHistoryModal(pid, playerName) {
$('#transfer-tracker-modal').remove();
const allHistory = JSON.parse(await GM_getValue(TRANSFER_HISTORY_KEY, '{}'));
const playerHistory = allHistory[pid] || [];
let tableRows = '';
const chartData = [];
let soldEntry = null;
if (playerHistory.length > 0) {
playerHistory.forEach(entry => {
const statusClass = `status-${entry.status.toLowerCase()}`;
const statusText = getText(`status${entry.status.charAt(0).toUpperCase() + entry.status.slice(1)}`) || entry.status;
const priceNum = parseInt((entry.askingPrice || '0').replace(/\D/g, ''), 10);
const bidNum = parseInt((entry.finalBid || '0').replace(/\D/g, ''), 10);
tableRows = `
<tr>
<td>${new Date(entry.timestamp).toLocaleString()}</td>
<td class="${statusClass}">${statusText}</td>
<td>${entry.askingPrice || '-'}</td>
<td>${entry.finalBid || '-'}</td>
<td>${entry.deadline || '-'}</td>
<td>${entry.buyer || '-'}</td>
</tr>` + tableRows;
if (priceNum > 0) {
chartData.push({
x: entry.timestamp,
y: priceNum,
name: `İstenen: ${entry.askingPrice}`
});
}
if (bidNum > 0) {
chartData.push({
x: entry.timestamp,
y: bidNum,
name: `Teklif: ${entry.finalBid}`
});
}
if (entry.status === 'sold') {
soldEntry = {
x: entry.timestamp,
y: bidNum,
name: `Satıldı: ${entry.finalBid}`
};
}
});
} else {
tableRows = `<tr><td colspan="6" style="text-align:center;">${getText('noHistory')}</td></tr>`;
}
chartData.sort((a, b) => a.x - b.x);
const modalHTML = `
<div id="transfer-tracker-modal" class="tt-modal-overlay">
<div class="tt-modal-content">
<div class="tt-modal-header">
<h3>${getText('historyFor')} ${playerName}</h3>
<span class="tt-modal-close">×</span>
</div>
<div class="tt-tab-container">
<div class="tt-tab active" data-tab="table">Tablo</div>
<div class="tt-tab" data-tab="chart">Grafik</div>
</div>
<div class="tt-modal-body">
<div id="tt-content-table" class="tt-tab-content active">
<table class="tt-history-table">
<thead>
<tr>
<th>${getText('date')}</th>
<th>${getText('status')}</th>
<th>${getText('price')}</th>
<th>${getText('bid')}</th>
<th>${getText('deadline')}</th>
<th>${getText('buyer')}</th>
</tr>
</thead>
<tbody>${tableRows}</tbody>
</table>
</div>
<div id="tt-content-chart" class="tt-tab-content">
<div id="tt-chart-container"></div>
</div>
</div>
<div class="tt-modal-footer">
<button id="tt-clear-history" class="mz-script-button" style="background-color: #dc3545;">${getText('clearHistory')}</button>
</div>
</div>
</div>`;
$('body').append(modalHTML);
$('.tt-tab').on('click', function() {
const tabId = $(this).data('tab');
$('.tt-tab').removeClass('active');
$(this).addClass('active');
$('.tt-tab-content').removeClass('active');
$('#tt-content-' + tabId).addClass('active');
});
if (playerHistory.length > 0) {
unsafeWindow.Highcharts.chart('tt-chart-container', {
chart: { type: 'line' },
title: { text: `${playerName} - Fiyat Değişimi` },
xAxis: {
type: 'datetime',
title: { text: 'Tarih' }
},
yAxis: {
title: { text: 'Fiyat (EUR)' },
labels: {
formatter: function() { return this.value.toLocaleString('de-DE') + ' EUR'; }
},
min: 0
},
tooltip: {
formatter: function() {
return `<b>${this.point.name}</b><br/>${new Date(this.x).toLocaleString()}`;
}
},
series: [{
name: 'Fiyat Geçmişi',
data: chartData,
step: 'left',
marker: {
enabled: true,
radius: 4
}
},
...(soldEntry ? [{
type: 'scatter',
name: 'Satış Noktası',
data: [soldEntry],
marker: {
symbol: 'diamond',
fillColor: '#28a745',
radius: 7,
lineWidth: 2,
lineColor: '#FFF'
}
}] : [])]
});
} else {
$('#tt-chart-container').html(`<div style="text-align:center; padding-top: 50px;">${getText('noHistory')}</div>`);
}
const modal = $('#transfer-tracker-modal');
modal.css('display', 'flex');
modal.find('.tt-modal-close').on('click', () => modal.remove());
modal.on('click', (e) => { if (e.target === modal[0]) modal.remove(); });
$('#tt-clear-history').on('click', async () => {
if(confirm(getText('confirmClear'))) {
const currentHistory = JSON.parse(await GM_getValue(TRANSFER_HISTORY_KEY, '{}'));
delete currentHistory[pid];
await GM_setValue(TRANSFER_HISTORY_KEY, JSON.stringify(currentHistory));
modal.remove();
}
});
}
function init() {
$('.playerContainer').each(function() {
const pid = $(this).find('span.player_id_span').text().trim();
const playerName = $(this).find('span.player_name').text().trim();
if (pid) {
const trackBtn = $(`<button class="transfer-track-btn" title="${getText('trackPlayer')}">T</button>`);
trackBtn.data('pid', pid).data('player-name', playerName);
$(this).find('h2.subheader .floatRight').append(trackBtn);
}
});
$(document).on('click', '.transfer-track-btn', function() {
const pid = $(this).data('pid');
const playerName = $(this).data('player-name');
showTransferHistoryModal(pid, playerName);
});
const wrapper = $('#script-controls-wrapper');
if (wrapper.length && $('#manual-transfer-check-btn').length === 0) {
const scanBtn = $(`<button id="manual-transfer-check-btn" class="sl-action-btn" style="background-color: #fd7e14;" title="Takip listenizdeki tüm oyuncuların transfer durumunu manuel olarak kontrol eder.">${getText('scanShortlistBtn')}</button>`);
scanBtn.on('mouseenter', function() { $(this).css('background-color', '#e67112'); });
scanBtn.on('mouseleave', function() { $(this).css('background-color', '#fd7e14'); });
wrapper.append(scanBtn);
scanBtn.on('click', function() {
if (window.isTransferCheckRunning) {
alert(lang === 'tr' ? 'Zaten devam eden bir tarama var.' : 'A scan is already in progress.');
return;
}
runBackgroundTransferCheck();
});
}
}
init();
}
/****************************************************************************************
* *
* BÖLÜM 6: ARKA PLAN TRANSFER KONTROLÜ (Background Transfer Check) - YENİ *
* *
****************************************************************************************/
async function runBackgroundTransferCheck() {
const lang = ($('meta[name="language"]').attr('content') || 'en') === 'tr' ? 'tr' : 'en';
const i18n = {
checkingShortlist: lang === 'tr' ? 'Takip listesi transferleri kontrol ediliyor...' : 'Checking shortlist transfers...',
checkComplete: lang === 'tr' ? 'Transfer kontrolü tamamlandı.' : 'Transfer check complete.',
};
if (window.isTransferCheckRunning) return;
window.isTransferCheckRunning = true;
const statusDiv = $('<div id="transfer-check-status" style="position:fixed; bottom:10px; right:10px; background: #00529B; color:white; padding: 8px 12px; border-radius:5px; z-index:99999; font-size:12px;"></div>').text(i18n.checkingShortlist);
$('body').append(statusDiv);
try {
const shortlistResp = await GM.xmlHttpRequest({ method: "GET", url: "https://www.managerzone.com/?p=shortlist" });
const shortlistDoc = new DOMParser().parseFromString(shortlistResp.responseText, 'text/html');
const playersOnShortlist = new Map();
$(shortlistDoc).find('.playerContainer').each(function() {
const pid = $(this).find('span.player_id_span').text().trim();
const playerLink = $(this).find('a[href*="&pid="]');
const name = playerLink.find('.player_name').text().trim();
if (pid && name) {
playersOnShortlist.set(pid, { name });
}
});
const allHistory = JSON.parse(await GM_getValue('mz_transfer_history_v2', '{}'));
let changed = false;
const playerIds = Array.from(playersOnShortlist.keys());
for (let i = 0; i < playerIds.length; i++) {
const pid = playerIds[i];
const playerInfo = playersOnShortlist.get(pid);
await new Promise(r => setTimeout(r, 400));
statusDiv.text(`${i18n.checkingShortlist} (${i + 1}/${playerIds.length}) - ${playerInfo.name}`);
const searchResp = await GM.xmlHttpRequest({ method: "GET", url: `https://www.managerzone.com/?p=transfer&u=${pid}&issearch=true` });
const searchDoc = new DOMParser().parseFromString(searchResp.responseText, 'text/html');
const playerOnTransfer = $(searchDoc).find(`#player_id_${pid}`);
const playerHistory = allHistory[pid] || [];
const lastEntry = playerHistory.length > 0 ? playerHistory[playerHistory.length - 1] : null;
if (playerOnTransfer.length > 0) {
const container = playerOnTransfer.closest('.playerContainer');
// DEĞİŞİKLİK BAŞLANGICI: Evrensel veri çekme
const transferInfoCells = container.find('.dg_transfer_info td');
let deadlineText = '', askingPriceText = '', bidText = '', bidderName = '-';
transferInfoCells.each(function(index) {
const label = $(this).text().trim();
if (/bitiş|deadline|frist|scadenza/i.test(label)) {
deadlineText = transferInfoCells.eq(index + 1).text().trim();
} else if (/fiyat|price|preis|prezzo/i.test(label)) {
askingPriceText = transferInfoCells.eq(index + 1).text().trim();
} else if (/teklif|bid|gebot|offerta/i.test(label)) {
const bidTd = transferInfoCells.eq(index + 1);
bidText = bidTd.contents().filter(function() { return this.nodeType === 3; }).text().trim();
const bidderLink = bidTd.find('a[href*="p=team"]');
if (bidderLink.length > 0) bidderName = bidderLink.text().trim();
}
});
// DEĞİŞİKLİK SONU
let currentStatus = (bidText && bidText !== '0' && bidText !== '0 EUR') ? 'bid' : 'listed';
if (!lastEntry || lastEntry.status === 'expired' || lastEntry.status === 'sold' || lastEntry.deadline !== deadlineText) {
const sellerLink = container.find('a[href*="p=team&tid="]');
const sellerTidMatch = sellerLink.attr('href').match(/tid=(\d+)/);
const sellerTid = sellerTidMatch ? sellerTidMatch[1] : null;
playerHistory.push({
timestamp: Date.now(),
status: currentStatus,
askingPrice: askingPriceText,
finalBid: bidText || '0',
deadline: deadlineText,
buyer: bidderName,
sellerTid: sellerTid
});
allHistory[pid] = playerHistory;
changed = true;
} else if (lastEntry.status.includes('listed') || lastEntry.status.includes('bid')) {
if (lastEntry.status !== currentStatus || lastEntry.finalBid !== bidText) {
lastEntry.status = currentStatus;
lastEntry.finalBid = bidText;
lastEntry.buyer = bidderName;
changed = true;
}
}
} else {
if (lastEntry && (lastEntry.status === 'listed' || lastEntry.status === 'bid')) {
const sellerTid = lastEntry.sellerTid;
const playerPageResp = await GM.xmlHttpRequest({method: "GET", url: `https://www.managerzone.com/?p=player&pid=${pid}`});
const playerDoc = new DOMParser().parseFromString(playerPageResp.responseText, 'text/html');
const teamLink = $(playerDoc).find('h1.win_title a[href*="p=team"]');
const newTidMatch = teamLink.length ? teamLink.attr('href').match(/tid=(\d+)/) : null;
const newTid = newTidMatch ? newTidMatch[1] : null;
if (newTid && sellerTid && newTid !== sellerTid) {
lastEntry.status = 'sold';
if (lastEntry.buyer === '-') {
lastEntry.buyer = teamLink.text().trim();
}
} else {
lastEntry.status = 'expired';
lastEntry.buyer = '-';
}
changed = true;
}
}
}
if (changed) {
await GM_setValue('mz_transfer_history_v2', JSON.stringify(allHistory));
}
await GM_setValue('mz_last_transfer_check', Date.now());
statusDiv.text(i18n.checkComplete).fadeOut(3000, () => statusDiv.remove());
} catch (error) {
console.error("Transfer check failed:", error);
statusDiv.text("Hata oluştu!").css('background', '#dc3545').fadeOut(3000, () => statusDiv.remove());
} finally {
window.isTransferCheckRunning = false;
}
}
/****************************************************************************************
* *
* ANA YÖNLENDİRİCİ (MASTER ROUTER) *
* *
****************************************************************************************/
function masterRouter() {
if (typeof $ === 'undefined' || typeof $.fn.on === 'undefined') {
console.log("SUITE: jQuery is not available yet. Waiting...");
setTimeout(masterRouter, 200);
return;
}
const params = new URLSearchParams(window.location.search);
const p_param = params.get('p');
const sub_param = params.get('sub');
const type_param = params.get('type');
const leagueTablePages = ['league', 'friendlyseries', 'private_cup', 'cup'];
if (leagueTablePages.includes(p_param)) {
console.log("SUITE: Initializing Advanced League Table...");
initializeLeagueTableScript();
}
const playerStatsPages_p = ['players', 'player'];
const playerStatsPages_sub = ['played', 'result', 'stats'];
if (playerStatsPages_p.includes(p_param) || (p_param === 'match' && playerStatsPages_sub.includes(sub_param))) {
console.log("SUITE: Initializing Player Stats...");
initializePlayerStatsScript();
}
if (p_param === 'league' && type_param === 'senior') {
console.log("SUITE: Initializing Play-off Predictor...");
initializePlayoffPredictorScript();
}
if (p_param === 'shortlist') {
console.log("SUITE: Initializing Shortlist Filter...");
initializeShortlistFilterScript();
console.log("SUITE: Initializing Transfer Tracker UI...");
initializeTransferTrackerScript();
}
}
const CHECK_INTERVAL = 12 * 60 * 60 * 1000;
const LAST_CHECK_KEY = 'mz_last_transfer_check';
const lastCheck = await GM_getValue(LAST_CHECK_KEY, 0);
if (Date.now() - lastCheck > CHECK_INTERVAL) {
console.log("SUITE: Otomatik transfer kontrol zamanı geldi. 10 saniye içinde başlatılacak.");
setTimeout(runBackgroundTransferCheck, 10000);
}
masterRouter();
})();