// ==UserScript== // @name Kadokado Magic Data Extractor // @namespace http://www.kadokado.com // @description Extract players data from a particular Kadokado clan (players scores, records, stars and levels for all games) to an Excel compliant file format (CSV) to make statistics. // @match http://www.kadokado.com/clan/* // @match http://www.kadokado.com/tid/* // @grant GM_xmlhttpRequest // @version 1.1.0 // @copyright 2012, Aldebaran Arthemys // @require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js // @require https://greasyfork.org/scripts/5392-waitforkeyelements/code/WaitForKeyElements.js // @grant GM_addStyle // @downloadURL none // ==/UserScript== var LOADING_IFRAME_ID = "KMDE_loadingFrame"; window.addEventListener ("load", mainContent, false); function mainContent() { var strPathName = window.location.pathname; if (window.top == window.self) { // Main behaviour : add the Extract button // Only applicable to "/clan/" pages if (strPathName.substring(0, 6) != "/clan/") { // Should be processed indirectly and only with the Advanced behaviour return true; } // Extracting basic data var strClanURL = RetrieveClanURL(); if (strClanURL == "") { //LogInfo("You must be a member of a clan to be allowed to extract its data!"); return false; } // Filter access var strLocation = document.location.href; if (strLocation.indexOf(strClanURL) == -1) return false; // Get the actions available on this clan through the buttons var ulActions = document.evaluate("//ul[@class='action']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (!ulActions) { LogInfo("Error 'mainContent' while retrieving the left menu"); return false; } // Add the Extract Data button CreateExtractAllButton(ulActions, "KMDE_Extract", "Extract all data", PrepareAdvancedExtraction); // Add the Extract History button only from the History page if (strPathName.slice(-8) == "/history") CreateExtractAllButton(ulActions, "KMDE_Extract_History", "Extract history data", ExtractHistory); } else { // Advanded behaviour: extracting data from the iframe // Ours only... in case of more than one in the pages if (window.name != LOADING_IFRAME_ID) { LogInfo("Warning : not expected iframe ; do nothing on this one."); return false; } // Check url for forum only if (strPathName.substring(0, 10) != "/tid/forum") { LogInfo("Abnormal behaviour: our iframe should be extracting forum data only"); return false; } // Wait for all AJAX calls before processing waitForKeyElements ("div[class*='tid_announce']", ExtractData, true, document); } return true; } // Returns Clan URL function RetrieveClanURL() { // Find Clan URL var buttonMyClan = document.evaluate("//div[@class='actions']/ul/li/a[starts-with(@href, '/clan/')]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (!buttonMyClan) return ""; return "http://www.kadokado.com" + buttonMyClan.getAttribute("href"); } // Create the Extract button function CreateExtractAllButton(buttonsArea, strIdButton, strButtonText, pFunction) { if (!buttonsArea) return null; var aNode = document.evaluate("./li[@id='" + strIdButton + "']/a", buttonsArea, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (!aNode) { var liNode = document.createElement("li"); liNode.setAttribute("id", strIdButton); buttonsArea.appendChild(liNode); aNode = document.createElement("a"); aNode.setAttribute("href","#"); //aNode.setAttribute("name","RunExtract"); aNode.onclick = pFunction; liNode.appendChild(aNode); var textNode = document.createTextNode(strButtonText); aNode.appendChild(textNode); } return aNode; } var m_strResult = ""; var m_nCount = 0; var m_strClanName = ""; // Extracting Data function PrepareAdvancedExtraction() { var strClanURL = RetrieveClanURL(); if (strClanURL == "") { LogInfo("You must be member of the clan to be allowed to extract its data!"); return false; } var titleNode = document.evaluate("//div[@class='clanBody']/h1/span[@class='txt2img']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (!titleNode) { LogInfo("Error 'PrepareAdvancedExtraction' while extracting the markup of the clan name"); return false; } var strClan = ExtractTextFromImages(titleNode); if (strClan == "") { LogInfo("Error 'PrepareAdvancedExtraction' while extracting the clan name"); return false; } //m_strClanName = strClan; if (!confirm("Ceci va lancer l'extraction des données du clan '" + strClan + "'\net proposera de télécharger le fichier de résultat.\n\nVoulez-vous continuer ?")) return false; // Prepare the iframe for the Advanced Behaviour var loadingIframe = document.createElement("iframe"); loadingIframe.hidden = true; loadingIframe.setAttribute("id", LOADING_IFRAME_ID); loadingIframe.setAttribute("name", LOADING_IFRAME_ID); document.body.appendChild(loadingIframe); // Looking for the forum thread "Records" loadingIframe.setAttribute("src", "http://www.kadokado.com/tid/forum#!view/32923|thread/12334041"); // The loading of this iframe causes the script to be reloaded with this page (second part of mainContent()) // and then to run the data extraction below } // French only for the moment function GetMonth(strMonth) { strMonth = strMonth.toLowerCase(); switch(strMonth) { case "janvier": return 1; case "février": return 2; case "mars": return 3; case "avril": return 4; case "mai": return 5; case "juin": return 6; case "juillet": return 7; case "août": return 8; case "septembre": return 9; case "octobre": return 10; case "novembre": return 11; case "décembre": return 12; }; return -1; } // french function GetDay(strDay) { strDay = strDay.toLowerCase(); switch(strDay) { case "dimanche": return 0; case "lundi": return 1; case "mardi": return 2; case "mercredi": return 3; case "jeudi": return 4; case "vendredi": return 5; case "samedi": return 6; }; return -1; } // french function GetDayName(nDay) { switch(nDay) { case 0: return "dimanche"; case 1: return "lundi"; case 2: return "mardi"; case 3: return "mercredi"; case 4: return "jeudi"; case 5: return "vendredi"; case 6: return "samedi"; }; return -1; } // french function GetFrDay(strDay) { strDay = strDay.toLowerCase(); var today = new Date(); var nDay = today.getDate(); if (strDay == "hier") return nDay-1; if (strDay == "aujourd'hui") return nDay; var nDayOfWeek = today.getDay(); var strDayOfToday = GetDayName(nDayOfWeek); var nOffsetDay = (GetDay(strDayOfToday) - GetDay(strDay) + 7) % 7; return nDay-nOffsetDay; } // French only for the moment function GetAbsoluteTime(strTimeStamp) { var strDateTime = ""; // Look for absolute dates/times var nTimeType = 0; var strPattern = ""; if (strTimeStamp.indexOf(":") == -1) { if (strTimeStamp.substring(0, 3) != "Il ") { nTimeType = 1; // absolute date strPattern = "Le ([0-3]?[0-9]) ([^0-9 ]*)( (20[0-9][0-9]))?"; } else { nTimeType = 3; // relative date strPattern = "Il y a( ([0-2]?[0-9]) h)?( ([0-5]?[0-9]) min)?"; } } else { nTimeType = 2; // absolute date with days strPattern = "([^0-9 ]*)( ([0-3]?[0-9]))? à ([0-2]?[0-9]):([0-5]?[0-9])"; } var regEx = new RegExp(strPattern, "i"); var date = 0; var today = new Date(); var nYear = today.getFullYear(); var nMonth = today.getMonth(); var nDay = today.getDate(); var nHour = 0; var nMinute = 0; var arrMatches = regEx.exec(strTimeStamp); switch(nTimeType) { case 1: if (arrMatches[4] != undefined) nYear = parseInt(arrMatches[4]); nDay = parseInt(arrMatches[1]); nMonth = GetMonth(arrMatches[2])-1; date = new Date(nYear, nMonth, nDay); break; case 2: if (arrMatches[3] != undefined) nDay = parseInt(arrMatches[3]); // absolute with date else nDay = GetFrDay(arrMatches[1]); // absolute with day of week; nHour = parseInt(arrMatches[4]); nMinute = parseInt(arrMatches[5]); date = new Date(nYear, nMonth, nDay, nHour, nMinute); break; case 3: { if (arrMatches[2] != undefined) nHour = parseInt(arrMatches[2]); // absolute with hour if (arrMatches[4] != undefined) nMinute = parseInt(arrMatches[4]); // absolute with minute var offsetTime = (nHour*60 + nMinute) * 60000; date = new Date(today - offsetTime); } break; }; return date.getFullYear() + "/" + (date.getMonth()+1) + "/" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes(); } var m_arrRecords = new Array(); var m_strRecordsLastUpdate = ""; // Extract all data function ExtractData(jNode) { console.log("Enter ExtractData"); // First extract records from the forum through this iframe var annonceNode = $(jNode)[0]; var aNodes = document.evaluate(".//a", annonceNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for(var i=0; i1]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for(var j=0; j m_currentDay) { nMonth = (nMonth-1) % 12; if (nMonth <= 0) { nMonth += 12 nYear -= 1; } // Update current values to avoid useless computations m_currentDay = nDay; m_currentMonth = nMonth; m_currentYear = nYear; } strDate = nDay.toString() + "/" + nMonth.toString() + "/" + nYear.toString() + " " + strHour; } break; case 1: // Type var imgNode = doc.evaluate("./img", tdNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (imgNode) { lType = 2; var strIcon = imgNode.getAttribute("src"); if (strIcon.slice(-13) == "/atksmall.gif") lType = 3; else if (strIcon.slice(-10) == "/sgame.gif") lType = 1; } break; case 2: // Player var aNode = doc.evaluate("./a", tdNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (aNode) { strPlayerName = aNode.childNodes[0].nodeValue; strPlayerURL = "http://www.kadokado.com" + aNode.getAttribute("href"); } else { lType = 4; } break; case 3: // Clan / Mission var childNode = doc.evaluate("*[1]", tdNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (childNode) { if (childNode.nodeName == "STRONG") { // Mission strClanName = childNode.childNodes[0].nodeValue; } else { // A / D var aNode = doc.evaluate("./a", childNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (aNode) { strClanName = aNode.childNodes[0].nodeValue; strClanURL = "http://www.kadokado.com" + aNode.getAttribute("href"); } } } break; case 4: // Game var gameNode = doc.evaluate("./span", tdNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (gameNode) { strGameName = gameNode.childNodes[0].nodeValue; } break; case 5: // Score var scoreNode = doc.evaluate("./span", tdNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (scoreNode) { lScore = parseInt(scoreNode.childNodes[0].nodeValue); } break; case 6: // Status if (tdNode.childNodes.length > 1) { var aNode = doc.evaluate("./a", tdNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (aNode) { strDefenderName = aNode.childNodes[0].nodeValue; strDefenderURL = "http://www.kadokado.com" + aNode.getAttribute("href"); } } break; case 7: // Points if (tdNode.hasChildNodes) { var imgNode = doc.evaluate("./img", tdNode, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (imgNode) { var strIcon = imgNode.getAttribute("src"); var bNegative = strIcon.indexOf("/moins.gif") != -1; var spanNodes = doc.evaluate("./span[@class='num2img']/img", tdNode, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); if (spanNodes.snapshotLength == 0) { LogInfo("Pas de Points trouvé "); continue; } var strPoints = ""; for(var k=0; k 2 && lPoints <= 0)); // Check alliances too break; } } m_strResult += "\"\";\"" + strDate + "\";\"" + lType.toString() + "\";\"" + strPlayerName + "\";\"" + strClanName + "\";\"" + strGameName + "\";\"" + lScore.toString() + "\";\"" + (bStatus?1:0) + "\";\"" + lPoints.toString() + "\";\"" + strDefenderName + "\";\"" + strPlayerURL + "\";\"" + strClanURL + "\";\"" + strDefenderURL + "\";\"Alertes\"\r\n"; } var nextNode = doc.evaluate("(//li[@class='next'])[1]/a", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (nextNode) { //alert(nextNode); var strNextPage = nextNode.getAttribute("href"); ExtractHistoryPage(strNextPage); } else { //LogInfo("Error 'ExtractHistory' while extracting the next page or end of process"); //return true; window.open(dataURI(m_strResult)); } console.log("Exit Extract History Page " + url); } }); console.log("Exit ExtractHistoryPage"); } function dataURI(s) { return 'data:text/csv,' + escape(s); } var m_arrGames = new Array(); function ExtractRecords(url, playerName, id, arrGameLevels) { console.log("Enter ExtractRecords"); GM_xmlhttpRequest({ method:"GET", url:url + "?sort=name", onload:function(response) { console.log("Enter Extract Member Record Data " + url); // Create document that you can query. var doc = parseHTML(response.responseText); if (!doc) { LogInfo("Error: doc parsing!"); return; } var listGames = doc.evaluate("//tr", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); if (listGames.snapshotLength == 0) { LogInfo("Pas de jeu trouvé"); return; } var myString = ""; var arrPlayerLevels = {} for(var i=-1; i<9; i++) arrPlayerLevels[i] = 0; var nCount = 0; for(var j in arrGameLevels) nCount++; for(var i=1; iopen'); window.open(dataURI(m_strResult)); } } }); console.log("Exit ExtractRecords"); } function LogInfo(myString) { alert(myString); } // Creates a document that can be queried with DOM methods. // Made by sizzlemctwizzle (http://userscripts.org/users/27715) ////////////////////////////////////////////////////////////////// function parseHTML(text) { var dt = document.implementation.createDocumentType('html', '-//W3C//DTD HTML 4.01 Transitional//EN', 'http://www.w3.org/TR/html4/loose.dtd'), doc = document.implementation.createDocument('', '', dt), html = doc.createElement('html'), head = doc.createElement('head'), body = doc.createElement('body'); if (!doc || !html || !body) return null; doc.appendChild(html); html.appendChild(head); body.innerHTML = text; html.appendChild(body); return doc; } ////////////////////////////////////////////////////////////////// function ExtractTextFromImages(parentNode) { if (!parentNode || parentNode.getAttribute("class").indexOf("2img") == -1) return ""; var strText = ""; for(var i=0; i 0) nLevel = parseInt(strLevelName.substring(nPos+1), 10); if (nLevel >= 0) nLevel = 8 - nLevel; return nLevel; } function ExtractPeriode(doc, arrPeriodParameters) { arrPeriodParameters[0] = -1; arrPeriodParameters[1] = -1; var divNode = doc.evaluate("//div[@class='kalendar']/div", doc, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (!divNode) return false; var regex = new RegExp("[^0-9]*([0-9]*)[^0-9]*([0-9]*)"); var arrResults = regex.exec(divNode.childNodes[0].nodeValue); if (arrResults.length != 3) return false; arrPeriodParameters[0] = arrResults[1]; arrPeriodParameters[1] = arrResults[2]; return true; } function ExtractClanPositions(doc, arrPositionParameters) { arrPositionParameters[0] = -1; arrPositionParameters[1] = -1; var listLiNodes = doc.evaluate("//ul[@class='statClan']/li[@class='rank']", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); if (listLiNodes.snapshotLength != 2) return false; for(var i=0; i