// ==UserScript==
// @name ylOppTacticsPreview (Modified)
// @namespace douglaskampl
// @version 5.0.1
// @description Shows the latest tactics used by an opponent
// @author kostrzak16 (feat. Douglas and xente)
// @match https://www.managerzone.com/?p=match&sub=scheduled
// @icon https://www.google.com/s2/favicons?sz=64&domain=managerzone.com
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant GM_getValue
// @grant GM_setValue
// @require https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js
// @resource oppTacticsPreviewStyles https://br18.org/mz/userscript/other/Slezsko.css
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
class OpponentTacticsPreview {
static CONSTANTS = {
MATCH_TYPE_GROUPS: {
'All': [
{ id: 'no_restriction', label: 'Senior' },
{ id: 'u23', label: 'U23' },
{ id: 'u21', label: 'U21' },
{ id: 'u18', label: 'U18' }
],
'World League': [
{ id: 'world_series', label: 'Senior WL' },
{ id: 'u23_world_series', label: 'U23 WL' },
{ id: 'u21_world_series', label: 'U21 WL' },
{ id: 'u18_world_series', label: 'U18 WL' }
],
'Official League': [
{ id: 'series', label: 'Senior League' },
{ id: 'u23_series', label: 'U23 League' },
{ id: 'u21_series', label: 'U21 League' },
{ id: 'u18_series', label: 'U18 League' }
]
},
URLS: {
MATCH_STATS: (matchId) => `https://www.managerzone.com/matchviewer/getMatchFiles.php?type=stats&mid=${matchId}&sport=soccer`,
MATCH_LIST: 'https://www.managerzone.com/ajax.php?p=matches&sub=list&sport=soccer',
PITCH_IMAGE: (matchId) => `https://www.managerzone.com/dynimg/pitch.php?match_id=${matchId}`,
MATCH_RESULT: (matchId) => `https://www.managerzone.com/?p=match&sub=result&mid=${matchId}`,
CLUBHOUSE: 'https://www.managerzone.com/?p=clubhouse'
},
STORAGE_KEYS: {
MATCH_LIMIT: 'ylopp_match_limit',
SAVED_TEAMS: 'ylopp_saved_teams',
USER_TEAM_ID: 'ylopp_user_team_id'
},
DEFAULTS: {
MATCH_LIMIT: 10,
MAX_SAVED_TEAMS: 15,
MAX_MATCH_LIMIT: 100
},
SELECTORS: {
FIXTURES_LIST: '#fixtures-results-list-wrapper',
STATS_XENTE: '#legendDiv',
ELO_SCHEDULED: '#eloScheduledSelect',
HOME_TEAM: '.home-team-column.flex-grow-1',
SELECT_WRAPPER: 'dd.set-default-wrapper'
},
};
constructor() {
this.ourTeamName = null;
this.userTeamId = null;
this.currentOpponentTid = '';
this.spinnerInstance = null;
this.observer = new MutationObserver(() => {
this.insertIconsAndListeners();
});
}
getMatchLimit() {
return GM_getValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.MATCH_LIMIT, OpponentTacticsPreview.CONSTANTS.DEFAULTS.MATCH_LIMIT);
}
setMatchLimit(limit) {
const numericLimit = parseInt(limit, 10);
if (!isNaN(numericLimit) && numericLimit > 0 && numericLimit <= OpponentTacticsPreview.CONSTANTS.DEFAULTS.MAX_MATCH_LIMIT) {
GM_setValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.MATCH_LIMIT, numericLimit);
}
}
getSavedTeams() {
return GM_getValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.SAVED_TEAMS, []);
}
saveTeam(teamId, teamName) {
if (!teamId || !teamName || teamName.startsWith('Team ')) {
return;
}
let teams = this.getSavedTeams();
const existingIndex = teams.findIndex(team => team.id === teamId);
if (existingIndex > -1) {
teams.splice(existingIndex, 1);
}
teams.unshift({ id: teamId, name: teamName });
const trimmedTeams = teams.slice(0, OpponentTacticsPreview.CONSTANTS.DEFAULTS.MAX_SAVED_TEAMS);
GM_setValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.SAVED_TEAMS, trimmedTeams);
}
startObserving() {
const fixturesList = document.querySelector(OpponentTacticsPreview.CONSTANTS.SELECTORS.FIXTURES_LIST);
if (fixturesList) {
this.observer.observe(fixturesList, { childList: true, subtree: true });
}
}
showLoadingSpinner() {
if (this.spinnerInstance) {
return;
}
const spinnerContainer = document.createElement('div');
spinnerContainer.id = 'spinjs-overlay';
document.body.appendChild(spinnerContainer);
this.spinnerInstance = new Spinner({ color: '#FFFFFF', lines: 12, top: '50%', left: '50%' }).spin(spinnerContainer);
}
hideLoadingSpinner() {
if (this.spinnerInstance) {
this.spinnerInstance.stop();
this.spinnerInstance = null;
}
const spinnerContainer = document.getElementById('spinjs-overlay');
if (spinnerContainer) {
spinnerContainer.remove();
}
}
extractTeamNameFromHtml(htmlDocument, teamId) {
const nameCounts = new Map();
const teamLinks = htmlDocument.querySelectorAll('.teams-wrapper a.clippable');
teamLinks.forEach(link => {
const linkUrl = new URL(link.href, location.href);
const linkTid = linkUrl.searchParams.get('tid');
if (linkTid === teamId) {
const fullName = link.querySelector('.full-name')?.textContent.trim();
if (fullName) {
nameCounts.set(fullName, (nameCounts.get(fullName) || 0) + 1);
}
}
});
if (nameCounts.size > 0) {
let mostCommonName = '';
let maxCount = 0;
for (const [name, count] of nameCounts.entries()) {
if (count > maxCount) {
maxCount = count;
mostCommonName = name;
}
}
return mostCommonName;
}
const boldTeamNameElement = htmlDocument.querySelector('.teams-wrapper a.clippable > strong > .full-name');
if (boldTeamNameElement) {
return boldTeamNameElement.textContent.trim();
}
return null;
}
async fetchLatestTactics(teamId, matchType) {
const modal = document.getElementById('interaction-modal');
if (modal) {
this.fadeOutAndRemove(modal);
}
this.showLoadingSpinner();
try {
const response = await fetch(
OpponentTacticsPreview.CONSTANTS.URLS.MATCH_LIST, {
method: 'POST',
headers: { 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },
body: `type=played&hidescore=false&tid1=${teamId}&offset=&selectType=${matchType}&limit=max`,
credentials: 'include'
}
);
if (!response.ok) {
throw new Error(`Network response was not ok: ${response.statusText}`);
}
const data = await response.json();
const parser = new DOMParser();
const htmlDocument = parser.parseFromString(data.list, 'text/html');
const actualTeamName = this.extractTeamNameFromHtml(htmlDocument, teamId);
const finalTeamName = actualTeamName || `Team ${teamId}`;
this.saveTeam(teamId, finalTeamName);
this.currentOpponentTid = teamId;
this.processTacticsData(htmlDocument, matchType, finalTeamName);
} catch (error) {
console.error('Failed to fetch latest tactics:', error);
} finally {
this.hideLoadingSpinner();
}
}
isRelevantMatch(entry) {
const wrapper = entry.querySelector('.responsive-hide.match-reference-text-wrapper');
if (!wrapper) {
return true;
}
const hasLink = wrapper.querySelector('a');
return hasLink !== null;
}
processTacticsData(htmlDocument, matchType, opponentName) {
const matchEntries = htmlDocument.querySelectorAll('dl > dd.odd');
const container = this.createTacticsContainer(matchType, opponentName);
document.body.appendChild(container);
const listWrapper = container.querySelector('.tactics-list');
let processedCount = 0;
const matchLimit = this.getMatchLimit();
for (const entry of matchEntries) {
if (processedCount >= matchLimit) {
break;
}
if (!this.isRelevantMatch(entry)) {
continue;
}
const link = entry.querySelector('a.score-shown');
if (!link) {
continue;
}
const dl = link.closest('dl');
const theScore = link.textContent.trim();
const homeTeamName = dl.querySelector('.home-team-column .full-name')?.textContent.trim() || 'Home';
const awayTeamName = dl.querySelector('.away-team-column .full-name')?.textContent.trim() || 'Away';
const mid = new URLSearchParams(new URL(link.href, location.href).search).get('mid');
if (!mid) {
continue;
}
let [homeGoals, awayGoals] = [0, 0];
if (theScore.includes('-')) {
const parts = theScore.split('-').map(x => parseInt(x.trim(), 10));
if (parts.length === 2 && !isNaN(parts[0]) && !isNaN(parts[1])) {
[homeGoals, awayGoals] = parts;
}
}
const opponentIsHome = (homeTeamName === opponentName);
const tacticUrl = OpponentTacticsPreview.CONSTANTS.URLS.PITCH_IMAGE(mid);
const resultUrl = OpponentTacticsPreview.CONSTANTS.URLS.MATCH_RESULT(mid);
const canvas = this.createCanvasWithReplacedColors(tacticUrl, opponentIsHome);
const item = document.createElement('div');
item.className = 'tactic-item';
const opponentGoals = opponentIsHome ? homeGoals : awayGoals;
const otherGoals = opponentIsHome ? awayGoals : homeGoals;
if (opponentGoals > otherGoals) {
item.classList.add('tactic-win');
} else if (opponentGoals < otherGoals) {
item.classList.add('tactic-loss');
} else {
item.classList.add('tactic-draw');
}
const linkA = document.createElement('a');
linkA.href = resultUrl;
linkA.target = '_blank';
linkA.className = 'tactic-link';
linkA.appendChild(canvas);
const scoreP = document.createElement('p');
scoreP.textContent = `${homeTeamName} ${theScore} ${awayTeamName}`;
linkA.appendChild(scoreP);
item.appendChild(linkA);
this.addPlaystyleHover(mid, canvas, this.currentOpponentTid);
listWrapper.appendChild(item);
processedCount++;
}
if (processedCount === 0) {
const message = document.createElement('div');
message.className = 'no-tactics-message';
message.textContent = 'No recent valid tactics found for this team and category.';
listWrapper.appendChild(message);
}
container.classList.add('fade-in');
}
showInteractionModal(teamId, sourceElement) {
const existingModal = document.getElementById('interaction-modal');
if (existingModal) {
this.fadeOutAndRemove(existingModal);
}
const modal = document.createElement('div');
modal.id = 'interaction-modal';
modal.classList.add('fade-in');
const header = document.createElement('div');
header.className = 'interaction-modal-header';
const title = document.createElement('span');
title.textContent = '';
header.appendChild(title);
const settingsIcon = document.createElement('span');
settingsIcon.className = 'settings-icon';
settingsIcon.innerHTML = '⚙';
header.appendChild(settingsIcon);
modal.appendChild(header);
const teamInputSection = this.createTeamInputSection(modal, teamId);
this.createTabbedButtons(modal, teamInputSection.teamIdInput);
const settingsPanel = this.createSettingsPanel(modal);
settingsIcon.onclick = () => {
settingsPanel.style.display = settingsPanel.style.display === 'block' ? 'none' : 'block';
};
document.body.appendChild(modal);
const rect = sourceElement.getBoundingClientRect();
modal.style.position = 'absolute';
modal.style.top = `${window.scrollY + rect.bottom + 5}px`;
modal.style.left = `${window.scrollX + rect.left}px`;
}
createTeamInputSection(container, initialTeamId) {
const section = document.createElement('div');
section.className = 'interaction-section team-input-section';
const label = document.createElement('label');
label.textContent = 'Team ID:';
label.htmlFor = 'team-id-input';
section.appendChild(label);
const teamIdInput = document.createElement('input');
teamIdInput.type = 'text';
teamIdInput.id = 'team-id-input';
teamIdInput.value = initialTeamId;
section.appendChild(teamIdInput);
const select = this.createRecentsDropdown(teamIdInput);
section.appendChild(select);
container.appendChild(section);
return { teamIdInput, recentsSelect: select };
}
createRecentsDropdown(teamIdInput) {
const select = document.createElement('select');
select.className = 'recents-select';
const defaultOption = document.createElement('option');
defaultOption.textContent = 'Recent Teams';
defaultOption.value = '';
select.appendChild(defaultOption);
this.getSavedTeams().forEach(team => {
const option = document.createElement('option');
option.value = team.id;
option.textContent = `${team.name} (${team.id})`;
select.appendChild(option);
});
select.onchange = () => {
if (select.value) {
teamIdInput.value = select.value;
}
};
return select;
}
createTabbedButtons(container, teamIdInput) {
const tabContainer = document.createElement('div');
tabContainer.className = 'tab-container';
const tabHeaders = document.createElement('div');
tabHeaders.className = 'tab-headers';
const tabContents = document.createElement('div');
tabContents.className = 'tab-contents';
Object.entries(OpponentTacticsPreview.CONSTANTS.MATCH_TYPE_GROUPS).forEach(([groupName, types], index) => {
const header = document.createElement('button');
header.className = 'tab-header';
header.textContent = groupName;
const content = document.createElement('div');
content.className = 'tab-content';
types.forEach(type => {
const button = document.createElement('button');
button.textContent = type.label;
button.onclick = () => {
const teamId = teamIdInput.value.trim();
if (teamId) {
this.fetchLatestTactics(teamId, type.id);
}
};
content.appendChild(button);
});
header.onclick = () => {
tabContainer.querySelectorAll('.tab-header').forEach(h => h.classList.remove('active'));
tabContainer.querySelectorAll('.tab-content').forEach(c => c.style.display = 'none');
header.classList.add('active');
content.style.display = 'flex';
};
tabHeaders.appendChild(header);
tabContents.appendChild(content);
if (index === 0) {
header.classList.add('active');
content.style.display = 'flex';
} else {
content.style.display = 'none';
}
});
tabContainer.appendChild(tabHeaders);
tabContainer.appendChild(tabContents);
container.appendChild(tabContainer);
}
createSettingsPanel(modalContainer) {
const panel = document.createElement('div');
panel.className = 'settings-panel';
panel.style.display = 'none';
const limitLabel = document.createElement('label');
limitLabel.textContent = `Match Limit (1-${OpponentTacticsPreview.CONSTANTS.DEFAULTS.MAX_MATCH_LIMIT}):`;
panel.appendChild(limitLabel);
const limitInput = document.createElement('input');
limitInput.type = 'text';
limitInput.inputMode = 'numeric';
limitInput.pattern = '[0-9]*';
limitInput.value = this.getMatchLimit();
limitInput.oninput = () => {
limitInput.value = limitInput.value.replace(/\D/g, '');
};
limitInput.onchange = () => this.setMatchLimit(limitInput.value);
panel.appendChild(limitInput);
modalContainer.appendChild(panel);
return panel;
}
createTacticsContainer(matchType, opponent) {
const existingContainer = document.getElementById('tactics-container');
if (existingContainer) {
this.fadeOutAndRemove(existingContainer);
}
const container = document.createElement('div');
container.id = 'tactics-container';
container.className = 'tactics-container';
const header = document.createElement('div');
header.className = 'tactics-header';
const title = document.createElement('div');
title.className = 'match-info-text';
let matchTypeLabel = matchType;
for (const group in OpponentTacticsPreview.CONSTANTS.MATCH_TYPE_GROUPS) {
const found = OpponentTacticsPreview.CONSTANTS.MATCH_TYPE_GROUPS[group].find(t => t.id === matchType);
if (found) {
matchTypeLabel = found.label;
break;
}
}
title.innerHTML = `<div class="title-main">${opponent} (${matchTypeLabel})</div>`;
header.appendChild(title);
const closeButton = document.createElement('button');
closeButton.className = 'close-button';
closeButton.textContent = '×';
closeButton.onclick = () => this.fadeOutAndRemove(container);
header.appendChild(closeButton);
container.appendChild(header);
const listWrapper = document.createElement('div');
listWrapper.className = 'tactics-list';
container.appendChild(listWrapper);
return container;
}
fadeOutAndRemove(el) {
if (!el) {
return;
}
el.classList.remove('fade-in');
el.classList.add('fade-out');
setTimeout(() => el.remove(), 200);
}
identifyUserTeamName() {
const ddRows = document.querySelectorAll('#fixtures-results-list > dd.odd');
if (ddRows.length === 0) {
return null;
}
const countMap = new Map();
ddRows.forEach(dd => {
const homeName = dd.querySelector('.home-team-column .full-name')?.textContent.trim();
const awayName = dd.querySelector('.away-team-column .full-name')?.textContent.trim();
if (homeName) {
countMap.set(homeName, (countMap.get(homeName) || 0) + 1);
}
if (awayName) {
countMap.set(awayName, (countMap.get(awayName) || 0) + 1);
}
});
for (const [name, count] of countMap.entries()) {
if (count === ddRows.length) {
return name;
}
}
return null;
}
insertIconsAndListeners() {
if (!this.ourTeamName) {
this.ourTeamName = this.identifyUserTeamName();
}
if (!this.ourTeamName) {
return;
}
document.querySelectorAll('dd.odd').forEach(dd => {
const selectWrapper = dd.querySelector(OpponentTacticsPreview.CONSTANTS.SELECTORS.SELECT_WRAPPER);
if (selectWrapper && !selectWrapper.querySelector('.magnifier-icon')) {
const homeTeamName = dd.querySelector('.home-team-column .full-name')?.textContent.trim();
const awayTeamName = dd.querySelector('.away-team-column .full-name')?.textContent.trim();
const homeTeamLink = dd.querySelector('.home-team-column a.clippable');
const awayTeamLink = dd.querySelector('.away-team-column a.clippable');
let opponentName = null,
opponentTid = null;
if (homeTeamName === this.ourTeamName && awayTeamName && awayTeamLink) {
opponentName = awayTeamName;
const awayHref = awayTeamLink.href;
if (awayHref) {
opponentTid = new URLSearchParams(new URL(awayHref, location.href).search).get('tid');
}
} else if (awayTeamName === this.ourTeamName && homeTeamName && homeTeamLink) {
opponentName = homeTeamName;
const homeHref = homeTeamLink.href;
if (homeHref) {
opponentTid = new URLSearchParams(new URL(homeHref, location.href).search).get('tid');
}
}
if (opponentName && opponentTid) {
const iconWrapper = document.createElement('span');
iconWrapper.className = 'magnifier-icon';
iconWrapper.dataset.tid = opponentTid;
iconWrapper.dataset.opponent = opponentName;
iconWrapper.title = 'Check opponent latest tactics';
iconWrapper.textContent = '🔍';
const select = selectWrapper.querySelector('select');
if (select) {
select.insertAdjacentElement('afterend', iconWrapper);
}
}
}
});
}
createCanvasWithReplacedColors(imageUrl, opponentIsHome) {
const canvas = document.createElement('canvas');
canvas.width = 150;
canvas.height = 200;
const context = canvas.getContext('2d');
const image = new Image();
image.crossOrigin = 'Anonymous';
image.onload = () => {
if (opponentIsHome) {
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(Math.PI);
context.translate(-canvas.width / 2, -canvas.height / 2);
}
context.drawImage(image, 0, 0, canvas.width, canvas.height);
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const darkGreen = { r: 0, g: 100, b: 0 };
for (let i = 0; i < data.length; i += 4) {
const r = data[i],
g = data[i + 1],
b = data[i + 2];
const isBlack = r < 30 && g < 30 && b < 30;
const isYellow = r > 200 && g > 200 && b < 100;
if (opponentIsHome) {
if (isYellow) {
data[i] = 0;
data[i + 1] = 0;
data[i + 2] = 0;
} else if (isBlack) {
data[i] = darkGreen.r;
data[i + 1] = darkGreen.g;
data[i + 2] = darkGreen.b;
}
} else {
if (isBlack) {
data[i] = 0;
data[i + 1] = 0;
data[i + 2] = 0;
} else if (isYellow) {
data[i] = darkGreen.r;
data[i + 1] = darkGreen.g;
data[i + 2] = darkGreen.b;
}
}
}
const tempData = new Uint8ClampedArray(data);
for (let y = 1; y < canvas.height - 1; y++) {
for (let x = 1; x < canvas.width - 1; x++) {
const i = (y * canvas.width + x) * 4;
if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0) {
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) {
continue;
}
const ni = ((y + dy) * canvas.width + (x + dx)) * 4;
if (!(data[ni] === 0 && data[ni + 1] === 0 && data[ni + 2] === 0)) {
tempData[i] = 255;
tempData[i + 1] = 255;
tempData[i + 2] = 255;
}
}
}
}
}
}
context.putImageData(new ImageData(tempData, canvas.width, canvas.height), 0, 0);
};
image.src = imageUrl;
return canvas;
}
async fetchPlaystyleChanges(mid, opponentTid) {
try {
const res = await fetch(OpponentTacticsPreview.CONSTANTS.URLS.MATCH_STATS(mid));
const txt = await res.text();
const xml = new DOMParser().parseFromString(txt, 'text/xml');
const changes = Array.from(xml.querySelectorAll('Events Tactic'))
.filter(n => n.getAttribute('teamId') === opponentTid)
.map(n => {
const tType = n.getAttribute('type');
if (['playstyle', 'aggression', 'tactic'].includes(tType)) {
return `Min ${n.getAttribute('time')}: ${tType} → ${n.getAttribute('new_setting')}`;
}
return null;
}).filter(Boolean);
return changes.length ? changes.join('<br>') : 'No relevant changes detected';
} catch (e) {
return 'Could not fetch playstyle data.';
}
}
addPlaystyleHover(mid, canvas, opponentTid) {
const tooltip = document.createElement('div');
tooltip.className = 'playstyle-tooltip';
document.body.appendChild(tooltip);
canvas.addEventListener('mouseover', async (ev) => {
tooltip.style.display = 'block';
tooltip.innerHTML = 'Loading...';
const info = await this.fetchPlaystyleChanges(mid, opponentTid);
tooltip.innerHTML = info;
tooltip.style.top = `${ev.pageY + 15}px`;
tooltip.style.left = `${ev.pageX + 5}px`;
});
canvas.addEventListener('mousemove', (ev) => {
tooltip.style.top = `${ev.pageY + 15}px`;
tooltip.style.left = `${ev.pageX + 5}px`;
});
canvas.addEventListener('mouseout', () => {
tooltip.style.display = 'none';
});
}
waitForEloValues() {
const interval = setInterval(() => {
const elements = document.querySelectorAll(OpponentTacticsPreview.CONSTANTS.SELECTORS.HOME_TEAM);
if (elements.length > 0 && elements[elements.length - 1]?.innerHTML.includes('br')) {
clearInterval(interval);
this.insertIconsAndListeners();
}
}, 100);
setTimeout(() => clearInterval(interval), 1500);
}
handleClickEvents(e) {
const clickedMagnifier = e.target.closest('.magnifier-icon');
if (clickedMagnifier) {
e.preventDefault();
e.stopPropagation();
const tid = clickedMagnifier.dataset.tid;
const name = clickedMagnifier.dataset.opponent;
if (!tid) {
return;
}
this.saveTeam(tid, name);
this.showInteractionModal(tid, clickedMagnifier);
return;
}
const interactionModal = document.getElementById('interaction-modal');
const tacticsContainer = document.getElementById('tactics-container');
if (interactionModal && !interactionModal.contains(e.target)) {
this.fadeOutAndRemove(interactionModal);
}
if (tacticsContainer && !tacticsContainer.contains(e.target)) {
this.fadeOutAndRemove(tacticsContainer);
}
}
async initializeUserTeamId() {
let storedId = GM_getValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.USER_TEAM_ID, null);
if (storedId) {
this.userTeamId = storedId;
return;
}
try {
const response = await fetch(OpponentTacticsPreview.CONSTANTS.URLS.CLUBHOUSE);
const text = await response.text();
const match = text.match(/dynimg\/badge\.php\?team_id=(\d+)/);
if (match && match[1]) {
this.userTeamId = match[1];
GM_setValue(OpponentTacticsPreview.CONSTANTS.STORAGE_KEYS.USER_TEAM_ID, this.userTeamId);
}
} catch (error) {
console.error('Could not fetch user team ID.', error);
}
}
async init() {
GM_addStyle(GM_getResourceText('oppTacticsPreviewStyles'));
await this.initializeUserTeamId();
const statsXenteRunning = document.querySelector(OpponentTacticsPreview.CONSTANTS.SELECTORS.STATS_XENTE);
const eloScheduledSelected = document.querySelector(OpponentTacticsPreview.CONSTANTS.SELECTORS.ELO_SCHEDULED)?.checked;
if (statsXenteRunning && eloScheduledSelected) {
this.waitForEloValues();
} else {
this.insertIconsAndListeners();
}
this.startObserving();
document.body.addEventListener('click', this.handleClickEvents.bind(this), true);
}
}
const プレビュー = new OpponentTacticsPreview();
プレビュー.init();
})();