您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays player ratings on transfer and player pages
当前为
- // ==UserScript==
- // @name MZ - Player Ratings from MZLive
- // @namespace douglaskampl
- // @version 1.7
- // @description Displays player ratings on transfer and player pages
- // @author Douglas
- // @match https://www.managerzone.com/?p=transfer*
- // @match https://www.managerzone.com/?p=players*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
- // @grant GM_addStyle
- // @run-at document-idle
- // @license MIT
- // ==/UserScript==
- (function () {
- 'use strict';
- const RATINGS = {
- "SPEED": { "K": 0.09, "D": 0.25, "A": 0.25, "M": 0.15, "W": 0.25, "F": 0.23 },
- "STAMINA": { "K": 0.09, "D": 0.16, "A": 0.18, "M": 0.15, "W": 0.20, "F": 0.15 },
- "PLAYINT": { "K": 0.09, "D": 0.07, "A": 0.05, "M": 0.10, "W": 0.06, "F": 0.05 },
- "PASSING": { "K": 0.02, "D": 0.02, "A": 0.05, "M": 0.15, "W": 0.04, "F": 0.04 },
- "SHOOTING": { "K": 0.00, "D": 0.00, "A": 0.00, "M": 0.00, "W": 0.05, "F": 0.28 },
- "HEADING": { "K": 0.00, "D": 0.00, "A": 0.02, "M": 0.00, "W": 0.00, "F": 0.03 },
- "GOALKEEPING": { "K": 0.55, "D": 0.00, "A": 0.00, "M": 0.00, "W": 0.00, "F": 0.00 },
- "BALLCONTROL": { "K": 0.09, "D": 0.08, "A": 0.10, "M": 0.12, "W": 0.15, "F": 0.15 },
- "TACKLING": { "K": 0.00, "D": 0.30, "A": 0.25, "M": 0.20, "W": 0.05, "F": 0.02 },
- "CROSSING": { "K": 0.02, "D": 0.07, "A": 0.05, "M": 0.08, "W": 0.15, "F": 0.00 },
- "SETPLAYS": { "K": 0.00, "D": 0.00, "A": 0.00, "M": 0.00, "W": 0.00, "F": 0.00 },
- "EXPERIENCE": { "K": 0.05, "D": 0.05, "A": 0.05, "M": 0.05, "W": 0.05, "F": 0.05 }
- };
- const SKILLS = [
- "SPEED",
- "STAMINA",
- "PLAYINT",
- "PASSING",
- "SHOOTING",
- "HEADING",
- "GOALKEEPING",
- "BALLCONTROL",
- "TACKLING",
- "CROSSING",
- "SETPLAYS",
- "EXPERIENCE",
- ];
- function calculateRatings(skills) {
- const player = { K: 0, D: 0, A: 0, M: 0, W: 0, F: 0, B: 0, top: 0 };
- SKILLS.forEach(skillName => {
- if (!skills[skillName] || !RATINGS[skillName]) return;
- const value = parseInt(skills[skillName], 10);
- if (isNaN(value)) return;
- if (skillName !== "EXPERIENCE") {
- player.B += value;
- }
- Object.keys(player).forEach(pos => {
- if (pos !== 'B' && pos !== 'top') {
- const weight = RATINGS[skillName][pos];
- if (typeof weight === 'number') {
- player[pos] += value * weight;
- if (player[pos] > player.top) {
- player.top = player[pos];
- }
- }
- }
- });
- });
- return {
- K: player.K.toFixed(2),
- D: player.D.toFixed(2),
- A: player.A.toFixed(2),
- M: player.M.toFixed(2),
- W: player.W.toFixed(2),
- F: player.F.toFixed(2),
- B: player.B,
- top: player.top.toFixed(2)
- };
- }
- function extractSkillsFromTable(skillsTable) {
- const skills = {};
- if (!skillsTable) return skills;
- const skillRows = skillsTable.querySelectorAll('tbody > tr');
- skillRows.forEach((row, index) => {
- if (index >= SKILLS.length) return;
- const valueElem = row.querySelector('td.skillval > span');
- if (valueElem) {
- const skillType = SKILLS[index];
- const value = valueElem.textContent.trim().replace(/[()]/g, '');
- if (skillType && value !== null && value !== '' && !isNaN(parseInt(value, 10))) {
- skills[skillType] = value;
- }
- }
- });
- return skills;
- }
- function extractPlayerSkillsDirectly(playerElement) {
- const skillsTable = playerElement.querySelector('.player_skills');
- if (skillsTable) {
- return extractSkillsFromTable(skillsTable);
- }
- return {};
- }
- function decodeHtmlEntities(text) {
- if (!text) return '';
- const textarea = document.createElement('textarea');
- textarea.innerHTML = text;
- return textarea.value;
- }
- function fetchSkillsFromTransfer(playerId) {
- return new Promise((resolve, reject) => {
- const url = `https://www.managerzone.com/ajax.php?p=transfer&sub=transfer-search&sport=soccer&issearch=true&u=${playerId}&nationality=all_nationalities&deadline=0&category=&valuea=&valueb=&bida=&bidb=&agea=19&ageb=37&birth_season_low=56&birth_season_high=74&tot_low=0&tot_high=110&s0a=0&s0b=10&s1a=0&s1b=10&s2a=0&s2b=10&s3a=0&s3b=10&s4a=0&s4b=10&s5a=0&s5b=10&s6a=0&s6b=10&s7a=0&s7b=10&s8a=0&s8b=10&s9a=0&s9b=10&s10a=0&s10b=10&s11a=0&s11b=10&s12a=0&s12b=10&o=0`;
- fetch(url, { credentials: 'include' })
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP Error: ${response.status}`);
- }
- return response.json();
- })
- .then(data => {
- if (data && data.players) {
- try {
- const decodedHtml = decodeHtmlEntities(data.players);
- const parser = new DOMParser();
- const ajaxDoc = parser.parseFromString(decodedHtml, 'text/html');
- const skillsTable = ajaxDoc.querySelector('.player_skills');
- if (skillsTable) {
- const skills = extractSkillsFromTable(skillsTable);
- if (Object.keys(skills).length > 0) {
- resolve(skills);
- } else {
- reject("Could not extract skills from the AJAX response table.");
- }
- } else {
- reject("Skills table not found in AJAX response.");
- }
- } catch (e) {
- console.error("Error parsing AJAX response:", e);
- reject("Error parsing AJAX response: " + e.message);
- }
- } else {
- reject("No player data found in AJAX response.");
- }
- })
- .catch(error => {
- console.error("Error during fetch request:", error);
- reject("Error during fetch request: " + error.message);
- });
- });
- }
- function createRatingDisplay(ratingsData) {
- const positions = [
- { code: 'K', name: 'Goalkeeper', value: ratingsData.K },
- { code: 'D', name: 'Defender', value: ratingsData.D },
- { code: 'A', name: 'Anchorman', value: ratingsData.A },
- { code: 'M', name: 'Midfielder', value: ratingsData.M },
- { code: 'W', name: 'Winger', value: ratingsData.W },
- { code: 'F', name: 'Forward', value: ratingsData.F }
- ];
- const container = document.createElement('div');
- container.className = 'mz-rating-container';
- const ratingsList = document.createElement('div');
- ratingsList.className = 'mz-rating-list';
- positions.forEach(pos => {
- const row = document.createElement('div');
- row.className = 'mz-rating-row';
- const isTop = pos.value === ratingsData.top;
- const posName = document.createElement('span');
- posName.className = 'mz-pos-name' + (isTop ? ' mz-pos-top' : '');
- posName.textContent = pos.name + ':';
- const posValue = document.createElement('span');
- posValue.className = 'mz-pos-value' + (isTop ? ' mz-pos-top' : '');
- posValue.textContent = pos.value;
- row.appendChild(posName);
- row.appendChild(posValue);
- ratingsList.appendChild(row);
- });
- container.appendChild(ratingsList);
- const infoRow = document.createElement('div');
- infoRow.className = 'mz-rating-info-row';
- infoRow.innerHTML = `<span>Total Balls: <strong>${ratingsData.B}</strong></span> <span>Top: <strong>${ratingsData.top}</strong></span>`;
- container.appendChild(infoRow);
- return container;
- }
- function shouldAddButton(playerElement) {
- const skillsTable = playerElement.querySelector('.player_skills');
- if (skillsTable && skillsTable.querySelector('tbody > tr > td.skillval > span')) {
- return true;
- }
- const isSinglePlayerPage = window.location.search.includes('pid=');
- const isOnTransferMarket = playerElement.querySelector('a[href*="p=transfer&sub=players&u="]');
- if (isSinglePlayerPage && isOnTransferMarket) {
- return true;
- }
- return false;
- }
- function addRatingButton(playerElement) {
- const idElement = playerElement.querySelector('.player_id_span');
- if (!idElement) {
- return;
- }
- const playerId = idElement.textContent.trim();
- if (!playerId) {
- return;
- }
- if (!shouldAddButton(playerElement)) {
- return;
- }
- if (idElement.parentNode.querySelector('.mz-rating-btn')) {
- return;
- }
- const btn = document.createElement('button');
- btn.className = 'mz-rating-btn';
- btn.innerHTML = '<i class="fa-solid fa-calculator"></i>';
- btn.title = 'Show player ratings';
- btn.dataset.playerId = playerId;
- let ratingContainer = null;
- let isVisible = false;
- let isLoading = false;
- btn.addEventListener('click', async (e) => {
- e.preventDefault();
- e.stopPropagation();
- if (isLoading) return;
- if (isVisible && ratingContainer) {
- ratingContainer.classList.remove('mz-rating-visible');
- setTimeout(() => {
- if (ratingContainer && ratingContainer.parentNode) {
- ratingContainer.parentNode.removeChild(ratingContainer);
- }
- ratingContainer = null;
- }, 300);
- isVisible = false;
- btn.innerHTML = '<i class="fa-solid fa-calculator"></i>';
- btn.title = 'Show player ratings';
- return;
- }
- isLoading = true;
- btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i>';
- btn.title = 'Loading ratings...';
- let skills = {};
- try {
- skills = extractPlayerSkillsDirectly(playerElement);
- if (Object.keys(skills).length === 0) {
- const isSinglePlayerPage = window.location.search.includes('pid=');
- const isOnTransferMarket = playerElement.querySelector('a[href*="p=transfer&sub=players&u="]');
- if (isSinglePlayerPage && isOnTransferMarket) {
- skills = await fetchSkillsFromTransfer(playerId);
- }
- }
- if (Object.keys(skills).length > 0) {
- const ratingsData = calculateRatings(skills);
- ratingContainer = createRatingDisplay(ratingsData);
- const playerHeader = playerElement.querySelector('.subheader');
- const targetElement = playerHeader ? playerHeader.nextSibling : playerElement.firstChild;
- playerHeader.parentNode.insertBefore(ratingContainer, targetElement);
- requestAnimationFrame(() => {
- requestAnimationFrame(() => {
- if (ratingContainer) ratingContainer.classList.add('mz-rating-visible');
- });
- });
- isVisible = true;
- btn.innerHTML = '<i class="fa-solid fa-xmark"></i>';
- btn.title = 'Hide player ratings';
- } else {
- btn.innerHTML = '<i class="fa-solid fa-triangle-exclamation"></i>';
- btn.title = 'Could not retrieve skills';
- setTimeout(() => {
- if (!isVisible) {
- btn.innerHTML = '<i class="fa-solid fa-calculator"></i>';
- btn.title = 'Show player ratings';
- }
- }, 2000);
- }
- } catch (error) {
- console.error(`Error getting ratings for player ${playerId}:`, error);
- btn.innerHTML = '<i class="fa-solid fa-triangle-exclamation"></i>';
- btn.title = `Error: ${error.message || error}`;
- setTimeout(() => {
- if (!isVisible) {
- btn.innerHTML = '<i class="fa-solid fa-calculator"></i>';
- btn.title = 'Show player ratings';
- }
- }, 3000);
- } finally {
- isLoading = false;
- if (!isVisible && !btn.innerHTML.includes('fa-triangle-exclamation')) {
- btn.innerHTML = '<i class="fa-solid fa-calculator"></i>';
- btn.title = 'Show player ratings';
- }
- }
- });
- idElement.parentNode.insertBefore(btn, idElement.nextSibling);
- }
- function processPlayerElements() {
- const playerContainers = document.querySelectorAll('div[id^="thePlayers_"]');
- playerContainers.forEach(container => {
- try {
- addRatingButton(container);
- } catch (e) {
- console.error("Error processing player container:", container, e);
- }
- });
- }
- function setUpObserver() {
- const targetNode = document.getElementById('players_container')
- || document.querySelector('.mainContent')
- || document.body;
- if (!targetNode) {
- console.error("MZ Ratings: Could not find a suitable node to observe for mutations.");
- return null;
- }
- const observer = new MutationObserver((mutations) => {
- let needsProcessing = false;
- mutations.forEach(mutation => {
- if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
- for (const node of mutation.addedNodes) {
- if (node.nodeType === Node.ELEMENT_NODE) {
- if ((node.id && node.id.startsWith('thePlayers_')) ||
- (node.querySelector && node.querySelector('div[id^="thePlayers_"]')))
- {
- needsProcessing = true;
- break;
- }
- }
- }
- }
- if(needsProcessing) return;
- });
- if (needsProcessing) {
- setTimeout(processPlayerElements, 200);
- }
- });
- observer.observe(targetNode, { childList: true, subtree: true });
- return observer;
- }
- function addStyles() {
- GM_addStyle(
- `.mz-rating-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- margin-left: 8px;
- width: 20px;
- height: 20px;
- border: none;
- border-radius: 50%;
- background: #1a73e8;
- color: white;
- cursor: pointer;
- font-size: 12px;
- line-height: 1;
- vertical-align: middle;
- transition: all 0.2s ease;
- box-shadow: 0 1px 3px rgba(0,0,0,0.15);
- padding: 0;
- }
- .mz-rating-btn:hover {
- background: #0d5bbb;
- transform: translateY(-1px);
- box-shadow: 0 2px 5px rgba(0,0,0,0.2);
- }
- .mz-rating-btn > i {
- font-size: 12px;
- line-height: 1;
- vertical-align: baseline;
- }
- .mz-rating-container {
- margin: 10px 0 5px 5px;
- padding: 10px 12px;
- background: #f8f9fa;
- border: 1px solid #e0e0e0;
- border-radius: 6px;
- box-shadow: 0 1px 4px rgba(0,0,0,0.08);
- width: fit-content;
- opacity: 0;
- max-height: 0;
- overflow: hidden;
- transform: translateY(-10px);
- transition: all 0.3s ease-out;
- }
- .mz-rating-visible {
- opacity: 1;
- max-height: 500px;
- transform: translateY(0);
- margin-bottom: 10px;
- }
- .mz-rating-list {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
- gap: 5px 10px;
- margin-bottom: 8px;
- }
- .mz-rating-row {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 2px 0px;
- font-size: 12px;
- }
- .mz-pos-name {
- color: #444;
- margin-right: 5px;
- }
- .mz-pos-value {
- font-weight: bold;
- color: #222;
- font-family: monospace;
- }
- .mz-pos-top {
- color: #1a73e8;
- font-weight: bold;
- }
- .mz-rating-info-row {
- margin-top: 8px;
- padding-top: 6px;
- border-top: 1px solid #e0e0e0;
- font-size: 11px;
- color: #555;
- display: flex;
- justify-content: space-between;
- }
- .mz-rating-info-row strong {
- color: #111;
- font-weight: 600;
- }`
- );
- }
- function init() {
- addStyles();
- processPlayerElements();
- setUpObserver();
- }
- if (document.readyState === 'complete' || document.readyState === 'interactive') {
- setTimeout(init, 300);
- } else {
- window.addEventListener('DOMContentLoaded', () => setTimeout(init, 300));
- }
- })();