match data importer

imports match data from compatible exports

目前为 2025-03-20 提交的版本。查看 最新版本

// ==UserScript==
// @name         match data importer
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  imports match data from compatible exports
// @author       Hendrik Steinmetz
// @match        https://homecourt.vcc.heimspiel.de/01_admin/spiele/*
// @match        https://homecourt.vcc.heimspiel.de/01_admin/spiele/spiel_aktionen.php
// @match        file:///C:/Users/hendr/Desktop/test.html
// @icon         https://www.google.com/s2/favicons?sz=64&domain=heimspiel.de
// @grant        GM_addStyle
// @license      GPLv3
// ==/UserScript==

let btn = document.createElement("button");
btn.id = "floatingButton";
btn.textContent = "Import";
btn.onclick = () => {
  document.querySelector("#customPopup").style.display = "block";
};

function triggerImport() {
  document.querySelector("#loadingSpinner").style.display = "block";
  const json = document.getElementById("popup-body").value;
  if (json) {
    const report = JSON.parse(json);
    if (!report) {
      document.querySelector("#loading-spinner").style.display = "none";
    }

    const homeTable = document.querySelectorAll("table")[0];
    const awayTable = document.querySelectorAll("table")[1];
    fillTable(
      homeTable,
      report.players?.filter((p) => p.home) ?? [],
      report.events.substitutions?.filter((sub) => sub.home) ?? [],
      report.events.cards?.filter((card) => card.home) ?? [],
    );
    fillTable(
      awayTable,
      report.players?.filter((p) => !p.home) ?? [],
      report.events.substitutions?.filter((sub) => !sub.home) ?? [],
      report.events.cards?.filter((card) => !card.home) ?? [],
    );
  }
  document.querySelector("#loadingSpinner").style.display = "none";
}

/**
 * @param table {HTMLTableElement}
 */
function fillTable(table, playerData, subData, cardData) {
  const rowArray = Array.from(table.rows);
  for (let i = 3; i < table.rows.length - 1; i++) {
    const currentRow = rowArray[i];
    const cells = Array.from(currentRow.cells);

    const numberCell = cells[0];
    const fullNameCell = cells[2];
    const startingCell = cells[4];
    const benchCell = cells[6];

    const subOnCell = cells[8];
    const subOnMinuteCell = cells[9];
    const subOnAddedCell = cells[10];

    const subOffCell = cells[12];
    const subOffMinuteCell = cells[13];
    const subOffAddedCell = cells[14];

    const yellowCell = cells[16];
    const yellowMinuteCell = cells[17];
    const yellowAddedCell = cells[18];

    const yellowRedCell = cells[20];
    const yellowRedMinuteCell = cells[21];
    const yellowRedAddedCell = cells[22];

    const redCell = cells[24];
    const redMinuteCell = cells[25];
    const redAddedCell = cells[26];

    const checkCell = (cell) => (cell.querySelector("input").checked = true);
    const uncheckCell = (cell) => (cell.querySelector("input").checked = false);
    const setCellValue = (cell, val) =>
      (cell.querySelector("input").value = val);

    const numberInput = numberCell.querySelector("input");
    if (!numberInput) {
      console.log("Number input null for", currentRow);
      continue;
    }

    const player = findPlayer(
      playerData,
      numberInput.value,
      fullNameCell.innerText ?? "",
    );
    if (!player) {
      uncheckCell(startingCell);
      uncheckCell(benchCell);
      continue;
    }

    let playerId = player.id;

    if (!player.sub) {
      checkCell(startingCell);
      uncheckCell(benchCell);
    } else if (player.sub) {
      uncheckCell(startingCell);
      checkCell(benchCell);
    }

    if (playerWasSubbedOn(playerId, subData)) {
      const substitution = subData.find((sub) => sub.on === playerId);
      checkCell(subOnCell);
      setCellValue(subOnMinuteCell, substitution.minute);
      setCellValue(subOnAddedCell, substitution.added ?? "");
    }

    if (playerWasSubbedOff(playerId, subData)) {
      const substitution = subData.find((sub) => sub.off === playerId);
      checkCell(subOffCell);
      setCellValue(subOffMinuteCell, substitution.minute);
      setCellValue(subOffAddedCell, substitution.added ?? "");
    }

    const cardEvents = cardData.filter((p) => p.id === playerId);
    if (cardEvents.length > 0) {
      cardEvents.forEach((event) => {
        if (event.card === "yellow") {
          checkCell(yellowCell);
          setCellValue(yellowMinuteCell, event.minute);
          setCellValue(yellowAddedCell, event.added ?? "");
        } else if (event.card === "yellow-red") {
          checkCell(yellowRedCell);
          setCellValue(yellowRedMinuteCell, event.minute);
          setCellValue(yellowRedAddedCell, event.added ?? "");
        } else if (event.card === "red") {
          checkCell(redCell);
          setCellValue(redMinuteCell, event.minute);
          setCellValue(redAddedCell, event.added ?? "");
        }
      });
    }

    //playerData = playerData.filter((p) => p.id !== playerId);
  }
}

/**
 * Finds players based on number and name:
 * - if no player with number is found: name is used
 * - if multiple with same number: use most similar name
 */
// TODO clean up and make more efficient
// ONLY ONE similarity computation per filter
// -> add as key and sort by
//
// one list filtered by number and one by name
// if only one in number list: return if sim > 0.5
// else if many in number list: sort by sim return first if sim > 0.5
// else if no number or none in number list: return first of name search if sim > 0.5
// return null
function findPlayer(players, number, nameFull) {
  const playersByNumber = players.filter(
    (player) => parseInt(player.number) === parseInt(number),
  );
  nameFull = nameFull.toLowerCase();
  const firstName = nameFull.split(" ")[0];
  const similiarityThreshold = 0.6;

  if (playersByNumber.length === 1 || !number) {
    if (
      number &&
      similarity(
        playersByNumber[0].name,
        //playersByNumber[0].name.split(" ")[0].toLowerCase(),
        nameFull,
      ) > similiarityThreshold
    ) {
      return playersByNumber[0];
    }

    const playersByName = players.filter((p) =>
      similarity(p.name.toLowerCase(), nameFull),
    );
    if (playersByName.length === 0) return null;

    playersByName.sort(
      (a, b) =>
        similarity(b.name.toLowerCase(), nameFull) -
        similarity(a.name.toLowerCase(), nameFull),
    );
    const firstScore = similarity(
      playersByName[0].name.toLowerCase(),
      nameFull,
    );

    if (firstScore > similiarityThreshold) {
      return playersByName[0];
    }
  }

  if (playersByNumber.length > 1) {
    playersByNumber.sort(
      (a, b) =>
        similarity(b.name.toLowerCase(), nameFull) -
        similarity(a.name.toLowerCase(), nameFull),
    );
    const firstScore = similarity(
      playersByNumber[0].name.toLowerCase(),
      nameFull,
    );

    if (firstScore > similiarityThreshold) {
      return playersByNumber[0];
    }
  }

  if (playersByNumber.length === 0) {
    console.log(players, number, nameFull);
    const nameSearch = players.filter(
      (p) => similarity(p.name.toLowerCase(), nameFull) > similiarityThreshold,
    );
    nameSearch.sort(
      (a, b) =>
        similarity(b.name.toLowerCase(), nameFull) -
        similarity(a.name.toLowerCase(), nameFull),
    );

    console.log("similarity search result", nameSearch);

    if (nameSearch.length === 0) {
      return null;
    }

    const firstScore = similarity(nameSearch[0].name.toLowerCase(), nameFull);

    return firstScore > similiarityThreshold ? nameSearch[0] : null;
  }

  return null;
}

function playerWasSubbedOn(playerId, subData) {
  return subData.filter((sub) => sub.on === playerId).length > 0;
}
function playerWasSubbedOff(playerId, subData) {
  return subData.filter((sub) => sub.off === playerId).length > 0;
}

let popup = document.createElement("div");
popup.id = "customPopup";
popup.innerHTML = `
<div class="popup-content">
  <h2 id="popup-title">Import</h2>
  <textarea id="popup-body"></textarea>
  <div id="loadingSpinner" class="spinner"></div>
  <button id="closePopup">Close</button>
  <button id="importBtn">Start import</button>
</div>
`;

document.addEventListener("click", function (event) {
  if (event.target.id === "closePopup") {
    document.getElementById("customPopup").style.display = "none";
  }
  if (event.target.id === "importBtn") {
    triggerImport();
  }
});

GM_addStyle(`
        #floatingButton {
            position: fixed;
            bottom: 20px;
            right: 20px;
            padding: 10px 20px;
            background-color: #007BFF;
            color: white;
            font-size: 16px;
            font-weight: bold;
            border: none;
            border-radius: 10px;
            box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
            cursor: pointer;
            z-index: 9999;
            transition: background-color 0.3s ease, transform 0.2s ease;
        }
        #floatingButton:hover {
            background-color: #0056b3;
            transform: scale(1.1);
        }
        #customPopup {
            display: none;
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 20px;
            width: 50%;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            border-radius: 10px;
            z-index: 10000;
        }
        #popup-title {
            text-align: center;
        }
        #popup-body {
            border: 1px solid #ccc;
            width: 100%;
            height: 120px;
            padding: 5px;
            font-size: 14px;
            border: none;
            resize: none;
            background: #f8f8f8;
            outline: none;
        }
        #closePopup {
            margin-top: 10px;
            padding: 8px 16px;
            background-color: #dc3545;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        #closePopup:hover {
            background-color: #c82333;
        }
        #importBtn {
            margin-top: 10px;
            margin-left: 10px;
            padding: 8px 16px;
            background-color: #007BFF;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
        #importBtn:hover {
            background-color: #0056b3;
        }
        .spinner {
            display: none;
            margin: 10px auto;
            width: 40px;
            height: 40px;
            border: 4px solid rgba(0, 0, 0, 0.2);
            border-top: 4px solid #007BFF;
            border-radius: 50%;
            animation: spin 1s linear infinite;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
    `);
document.body.appendChild(btn);
document.body.appendChild(popup);

function similarity(s1, s2) {
  var longer = s1;
  var shorter = s2;
  if (s1.length < s2.length) {
    longer = s2;
    shorter = s1;
  }
  var longerLength = longer.length;
  if (longerLength == 0) {
    return 1.0;
  }
  return (
    (longerLength - editDistance(longer, shorter)) / parseFloat(longerLength)
  );
}

function editDistance(s1, s2) {
  s1 = s1.toLowerCase();
  s2 = s2.toLowerCase();

  var costs = new Array();
  for (var i = 0; i <= s1.length; i++) {
    var lastValue = i;
    for (var j = 0; j <= s2.length; j++) {
      if (i == 0) costs[j] = j;
      else {
        if (j > 0) {
          var newValue = costs[j - 1];
          if (s1.charAt(i - 1) != s2.charAt(j - 1))
            newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1;
          costs[j - 1] = lastValue;
          lastValue = newValue;
        }
      }
    }
    if (i > 0) costs[s2.length] = lastValue;
  }
  return costs[s2.length];
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址