Comment Scraper

MacOS like comment scrape into discord webhook

  1. // ==UserScript==
  2. // @name Comment Scraper
  3. // @namespace discord.gg/-----
  4. // @version 2
  5. // @description MacOS like comment scrape into discord webhook
  6. // @author Simon (dork)
  7. // @match https://www.kogama.com/games/play/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. "use strict";
  13.  
  14. const MAX_FILE_SIZE = 8 * 1024 * 1024; // 9mb Webhook file limit | DO NOT CHANGE
  15. let processedRequests = 0;
  16. let totalPages = 0;
  17. let isMenuVisible = true;
  18.  
  19. const extractGameIdFromUrl = () => {
  20. const match = window.location.pathname.match(/\/games\/play\/([^/]+)\//);
  21. return match ? match[1] : null;
  22. };
  23.  
  24. const extractGameTitle = () => {
  25. const titleElement = document.querySelector("section._10ble h1.game-title");
  26. return titleElement ? titleElement.textContent.trim() : "Unknown Game";
  27. };
  28.  
  29. const createMenu = () => {
  30. const style = document.createElement("style");
  31. style.innerHTML = `
  32. #u7465 {
  33. position: fixed;
  34. top: 20px;
  35. left: 20px;
  36. width: 280px;
  37. background: #fff;
  38. color: #333;
  39. border-radius: 12px;
  40. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  41. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
  42. z-index: 9999;
  43. transition: all 0.3s ease;
  44. display: block;
  45. padding: 20px;
  46. }
  47. #u7465 .top-bar {
  48. height: 30px;
  49. background: #f1f1f1;
  50. border-top-left-radius: 12px;
  51. border-top-right-radius: 12px;
  52. display: flex;
  53. justify-content: flex-start;
  54. align-items: center;
  55. padding: 0 10px;
  56. cursor: move;
  57. }
  58. #u7465 .top-bar .button {
  59. width: 12px;
  60. height: 12px;
  61. border-radius: 50%;
  62. background-color: #ff5f57;
  63. margin-right: 6px;
  64. cursor: pointer;
  65. }
  66. #u7465 .top-bar .minimize {
  67. background-color: #ffbd2e;
  68. }
  69. #u7465 .top-bar .close {
  70. background-color: #ff5f57;
  71. }
  72. #u7465 .top-bar .maximize {
  73. background-color: #27c93f;
  74. }
  75. #u7465 .top-bar .button:hover {
  76. opacity: 0.7;
  77. }
  78. #u7465 h1 {
  79. font-size: 18px;
  80. margin: 10px;
  81. color: #333;
  82. text-align: center;
  83. font-weight: 600;
  84. }
  85. #u7465 input, #u7465 button {
  86. width: calc(100% - 20px);
  87. padding: 10px;
  88. margin: 8px 0;
  89. border-radius: 8px;
  90. border: 1px solid #ddd;
  91. box-sizing: border-box;
  92. background-color: #f9f9f9;
  93. font-size: 14px;
  94. color: #333;
  95. }
  96. #u7465 input:focus, #u7465 button:focus {
  97. outline: none;
  98. border-color: #007aff;
  99. }
  100. #u7465 button {
  101. background: linear-gradient(45deg, #ff79c6, #ff9a8b, #9b59b6);
  102. color: white;
  103. cursor: pointer;
  104. transition: transform 0.2s, background-color 0.2s;
  105. border: none;
  106. }
  107. #u7465 button:hover {
  108. transform: scale(1.05);
  109. background-color: #d45e9b;
  110. }
  111. #u7465 button:active {
  112. background-color: #c34b7a;
  113. }
  114. #u7465 #progress {
  115. font-size: 14px;
  116. text-align: center;
  117. margin-top: 10px;
  118. color: #555;
  119. }
  120. `;
  121. document.head.appendChild(style);
  122.  
  123. const menu = document.createElement("div");
  124. menu.id = "u7465";
  125. menu.innerHTML = `
  126. <div class="top-bar">
  127. <div class="button close"></div>
  128. <div class="button minimize"></div>
  129. <div class="button maximize"></div>
  130. </div>
  131. <h1>Comment Scraper</h1>
  132. <input id="webhook-url" type="text" placeholder="Webhook URL">
  133. <input id="total-pages" type="number" placeholder="Total Pages">
  134. <button id="start-button">Start Scraping</button>
  135. <div id="progress">Progress: 0 / 0</div>
  136. `;
  137. document.body.appendChild(menu);
  138.  
  139. makeDraggable(menu);
  140.  
  141. document.getElementById("total-pages").addEventListener("input", (e) => {
  142. totalPages = parseInt(e.target.value, 10);
  143. updateProgress();
  144. });
  145.  
  146. document
  147. .querySelector(".close")
  148. .addEventListener("click", () => toggleMenuVisibility(false));
  149. document
  150. .querySelector(".minimize")
  151. .addEventListener("click", () => toggleMenuVisibility(true));
  152. };
  153.  
  154. const toggleMenuVisibility = (isVisible) => {
  155. const menu = document.getElementById("u7465");
  156. menu.style.display = isVisible ? "block" : "none";
  157. isMenuVisible = isVisible;
  158. };
  159.  
  160. const makeDraggable = (element) => {
  161. let isDragging = false,
  162. startX,
  163. startY,
  164. initialX,
  165. initialY;
  166. const topBar = element.querySelector(".top-bar");
  167. topBar.addEventListener("mousedown", (e) => {
  168. isDragging = true;
  169. startX = e.clientX;
  170. startY = e.clientY;
  171. initialX = element.offsetLeft;
  172. initialY = element.offsetTop;
  173. document.addEventListener("mousemove", onDrag);
  174. document.addEventListener("mouseup", onStopDrag);
  175. });
  176.  
  177. const onDrag = (e) => {
  178. if (!isDragging) return;
  179. const dx = e.clientX - startX;
  180. const dy = e.clientY - startY;
  181. element.style.left = `${initialX + dx}px`;
  182. element.style.top = `${initialY + dy}px`;
  183. };
  184.  
  185. const onStopDrag = () => {
  186. isDragging = false;
  187. document.removeEventListener("mousemove", onDrag);
  188. document.removeEventListener("mouseup", onStopDrag);
  189. };
  190. };
  191.  
  192. const fetchPage = async (url) => {
  193. const response = await fetch(url);
  194. if (response.ok) {
  195. processedRequests++;
  196. updateProgress();
  197. return await response.json();
  198. } else {
  199. throw new Error(`Failed to fetch ${url}`);
  200. }
  201. };
  202.  
  203. const sendFileToWebhook = async (webhookUrl, fileData, fileName) => {
  204. const blob = new Blob([fileData], { type: "text/plain" });
  205. const formData = new FormData();
  206. formData.append("file", blob, fileName);
  207.  
  208. const response = await fetch(webhookUrl, {
  209. method: "POST",
  210. body: formData,
  211. });
  212.  
  213. if (!response.ok) {
  214. throw new Error("Failed to send file");
  215. }
  216.  
  217. console.log("File sent successfully");
  218. };
  219.  
  220. const formatCommentData = (comment) => {
  221. const content = JSON.parse(comment._data).data || "No Content";
  222. const createdAt = new Date(comment.created).toLocaleString();
  223. return `[${createdAt}] ${comment.profile_username} (${comment.profile_id}): ${content}`;
  224. };
  225.  
  226. const generateMetadata = () => {
  227. const date = new Date().toLocaleString();
  228. const gameId = extractGameIdFromUrl();
  229. return `Date: ${date}\nGame ID: ${gameId}\nTotal Pages: ${totalPages}\n\n▃▃▃▃▃▃▃▃▃▃▃▃▃▃▃▃▃\n\n `;
  230. };
  231.  
  232. const updateProgress = () => {
  233. const progressText = `${processedRequests} / ${totalPages}`;
  234. document.getElementById(
  235. "progress"
  236. ).textContent = `Progress: ${progressText}`;
  237. };
  238.  
  239. const processAllComments = async (webhookUrl) => {
  240. const fetchPromises = [];
  241. let currentPage = 1;
  242.  
  243. while (currentPage <= totalPages) {
  244. const pageUrl = `https://www.kogama.com/game/${extractGameIdFromUrl()}/comment/?page=${currentPage}&count=400`;
  245. fetchPromises.push(fetchPage(pageUrl));
  246. currentPage++;
  247. }
  248.  
  249. try {
  250. const allPageResults = await Promise.all(fetchPromises);
  251. let allComments = [];
  252. allPageResults.forEach((result) => {
  253. if (result.data) {
  254. allComments = allComments.concat(result.data);
  255. }
  256. });
  257.  
  258. allComments.sort((a, b) => new Date(b.created) - new Date(a.created));
  259.  
  260. const formattedData = allComments.map(formatCommentData).join("\n");
  261. const totalComments = formattedData.split("\n").length;
  262.  
  263. let currentFileData = generateMetadata();
  264. let currentFileSize = 0;
  265. let fileCount = 1;
  266. a;
  267.  
  268. let fileDataBuffer = currentFileData + formattedData;
  269.  
  270. if (new Blob([fileDataBuffer]).size > MAX_FILE_SIZE) {
  271. const gameTitle = extractGameTitle().replace(/[\/\\?%*:|"<>]/g, "_");
  272. await sendFileToWebhook(
  273. webhookUrl,
  274. fileDataBuffer,
  275. `${gameTitle}_comments_${fileCount}.txt`
  276. );
  277. fileCount++;
  278. fileDataBuffer = formattedData;
  279. }
  280.  
  281. if (fileDataBuffer) {
  282. const gameTitle = extractGameTitle().replace(/[\/\\?%*:|"<>]/g, "_");
  283. await sendFileToWebhook(
  284. webhookUrl,
  285. fileDataBuffer,
  286. `${gameTitle}_comments_${fileCount}.txt`
  287. );
  288. }
  289.  
  290. console.log("All comments processed and files sent!");
  291. } catch (err) {
  292. console.error("Error processing comments:", err);
  293. }
  294. };
  295.  
  296. const startProcess = async () => {
  297. const webhookUrl = document.getElementById("webhook-url").value;
  298.  
  299. if (!webhookUrl || isNaN(totalPages) || totalPages <= 0) {
  300. alert("Please fill all fields correctly.");
  301. return;
  302. }
  303.  
  304. processedRequests = 0;
  305. updateProgress();
  306.  
  307. await processAllComments(webhookUrl);
  308. };
  309.  
  310. createMenu();
  311. document
  312. .getElementById("start-button")
  313. .addEventListener("click", startProcess);
  314. })();