Soccerway Match Data Exporter

Exports match data from Soccerway match reports

  1. // ==UserScript==
  2. // @name Soccerway Match Data Exporter
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2.1
  5. // @description Exports match data from Soccerway match reports
  6. // @author Hendrik Steinmetz
  7. // @match https://*.soccerway.com/matches/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=soccerway.com
  9. // @grant GM_addStyle
  10. // @grant GM_setClipboard
  11. // @license GPLv3
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. "use strict";
  16.  
  17. let btn = document.createElement("button");
  18. btn.id = "floatingButton";
  19. btn.textContent = "Export";
  20. btn.onclick = async function () {
  21. btn.textContent = "Loading...";
  22. await generateMatchData();
  23. const report = {
  24. source: "soccerway",
  25. players,
  26. events,
  27. };
  28. GM_setClipboard(JSON.stringify(report), "json");
  29. if (report.players.length > 0) {
  30. btn.style.backgroundColor = "#28a745";
  31. btn.textContent = "Copied!";
  32. setTimeout(() => {
  33. btn.textContent = "Export";
  34. btn.style.backgroundColor = "#007BFF";
  35. }, 1500);
  36. }
  37. };
  38.  
  39. const players = [];
  40. const events = {
  41. goals: [],
  42. substitutions: [],
  43. cards: [],
  44. };
  45.  
  46. function handlePlayerRow(row, isHome, isSub) {
  47. console.log(row);
  48.  
  49. if (row.innerText.startsWith("Trainer")) return;
  50.  
  51. const bookings = row.querySelector(".bookings");
  52. const subEvent =
  53. row.querySelector(".substitute-in") &&
  54. row.querySelector(".substitute-out");
  55. let playerName = row
  56. .querySelector(".player")
  57. .innerText.split("for")[0]
  58. .trim();
  59. const playerObj = {
  60. id: row.querySelectorAll("a")[0].href,
  61. name: playerName,
  62. number: row.querySelector(".shirtnumber").innerText.trim(),
  63. home: isHome,
  64. sub: isSub,
  65. };
  66. players.push(playerObj);
  67.  
  68. if (isSub && subEvent) {
  69. const subIn = row.querySelector(".substitute-in a");
  70. const subOut = row.querySelector(".substitute-out a");
  71. let minuteText = row
  72. .querySelector(".substitute-out")
  73. .innerText.trim()
  74. .split(" ")
  75. .pop();
  76. minuteText = minuteText.replace("'", "");
  77. const minute = minuteText.split("+")[0];
  78. const added = minuteText.includes("+") ? minuteText.split("+")[1] : null;
  79.  
  80. events.substitutions.push({
  81. home: isHome,
  82. minute,
  83. added,
  84. on: subIn.href,
  85. off: subOut.href,
  86. });
  87. }
  88.  
  89. if (bookings) {
  90. const arr = Array.from(bookings.querySelectorAll("span"));
  91. arr.forEach((booking) => {
  92. const imgUrl = booking.querySelector("img").src;
  93. const time = booking.innerText.trim().replace("'", "");
  94. const added = time.includes("+") ? time.split("+")[1] : null;
  95. const minute = time.split("+")[0];
  96.  
  97. const filename = imgUrl.split("/").pop();
  98.  
  99. if (filename.includes("G.png")) {
  100. events.goals.push({
  101. home: isHome,
  102. minute,
  103. added,
  104. player: playerObj.id,
  105. });
  106. } else if (filename.includes("YC.png")) {
  107. events.cards.push({
  108. home: isHome,
  109. minute,
  110. added,
  111. player: playerObj.id,
  112. type: "yellow",
  113. });
  114. } else if (filename.includes("RC.png")) {
  115. events.cards.push({
  116. home: isHome,
  117. minute,
  118. added,
  119. player: playerObj.id,
  120. type: "red",
  121. });
  122. } else if (filename.includes("Y2C.png")) {
  123. events.cards.push({
  124. home: isHome,
  125. minute,
  126. added,
  127. player: playerObj.id,
  128. type: "yellow-red",
  129. });
  130. }
  131. });
  132. }
  133. }
  134.  
  135. async function generateMatchData() {
  136. players.length = 0;
  137. events.cards.length = 0;
  138. events.goals.length = 0;
  139. events.substitutions.length = 0;
  140. const containers = document.querySelectorAll("table.lineups");
  141.  
  142. const homePlayersStarting = containers[0].rows;
  143. const awayPlayersStarting = containers[1].rows;
  144. const homePlayersSubs = containers[2].rows;
  145. const awayPlayersSubs = containers[3].rows;
  146.  
  147. Array.from(homePlayersStarting)
  148. .slice(1, -1)
  149. .forEach((row) => handlePlayerRow(row, true, false));
  150.  
  151. Array.from(awayPlayersStarting)
  152. .slice(1, -1)
  153. .forEach((row) => handlePlayerRow(row, false, false));
  154.  
  155. Array.from(homePlayersSubs)
  156. .slice(1)
  157. .forEach((row) => handlePlayerRow(row, true, true));
  158.  
  159. Array.from(awayPlayersSubs)
  160. .slice(1)
  161. .forEach((row) => handlePlayerRow(row, false, true));
  162. }
  163.  
  164. GM_addStyle(`
  165. #floatingButton {
  166. position: fixed;
  167. bottom: 20px;
  168. right: 20px;
  169. padding: 10px 20px;
  170. background-color: #007BFF;
  171. color: white;
  172. font-size: 16px;
  173. font-weight: bold;
  174. border: none;
  175. border-radius: 10px;
  176. box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
  177. cursor: pointer;
  178. z-index: 9999;
  179. transition: background-color 0.3s ease, transform 0.2s ease;
  180. }
  181. #floatingButton:hover {
  182. background-color: #0056b3;
  183. transform: scale(1.1);
  184. }`);
  185. document.body.appendChild(btn);
  186. })();

QingJ © 2025

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