Last active
June 20, 2018 04:09
-
-
Save cuylerstuwe/8834ab24f15d5c8d0b3a7de91ad7fcc7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Hit Notifier 2 With web+panda:// | |
| // @namespace http://tampermonkey.net/ | |
| // @version 7.1 | |
| // @description Kadauchi's Hit Notifier website, made more usable. Latest update: Changing paths, uploading to GitHub Gist. | |
| // @author Cuyler Stuwe (salembeats) | |
| // @include http*://hitnotifier.com* | |
| // @grant GM_notification | |
| // @grant GM_xmlhttpRequest | |
| // @grant GM_openInTab | |
| // @connect mturk.com | |
| // @connect worker.mturk.com | |
| // @connect amazon.com | |
| // @require https://gist.github.com/salembeats/a63aff6e6096830145ce209174e3e3de/raw/e9e0b6d4dc763736280b7d532a6b8f7292d4e452/mturk-export-parser.library.user.js | |
| // @icon https://i.imgur.com/snRSm80.gif | |
| // ==/UserScript== | |
| const SCRIPT_GREASYFORK_URL = `https://greasyfork.org/en/scripts/37558-hit-notifier-2-with-web-panda`; // TODO: Put actual URL here when the script is done. | |
| const SALEM_BEATS_ICON_URL = "https://i.imgur.com/snRSm80.gif"; | |
| const SHOULD_OPEN_NOTIFICATION_HITS_IN_BACKGROUND_TAB = false; // Currently, it seems that this is the best default hard-coded setting for notifications. | |
| const SHOULD_OPEN_CLICKED_WEBPANDA_LINKS_IN_BACKGROUND_TAB = true; | |
| const WEB_PANDA_WINDOW_WIDTH = 500; | |
| const WEB_PANDA_WINDOW_HEIGHT = 350; | |
| const NEW_HEADER_HTML = ` | |
| <div class="hn2-title">HIT Notifier <a href="${SCRIPT_GREASYFORK_URL}" target="_blank"><span id="versionNumber" style="transition: 0.33s all linear;">2</span></a></div> | |
| <div class="hn2-subtitle"><a href="https://paypal.me/paytherobot" target="_blank" class="pay-me-link">Kadauchi</a>'s Hit Notifier, Modified by <a href="https://paypal.me/salembeats" target="_blank" class="pay-me-link">Cuyler Stuwe (salembeats)</a>.</div> | |
| <div class="hn2-subtitle">Featuring web+panda:// links!</div> | |
| <div class="hn2-subtitle-tip">(You can open these with the <a href="https://greasyfork.org/en/scripts/36558-web-panda-default-handler" target="_blank">Default Handler</a>, <a href="https://greasyfork.org/en/scripts/37543-web-panda-ham" target="_blank">Web Panda Ham</a>, or <a href="https://greasyfork.org/en/scripts/37346-simple-panda-crazy-helper-web-panda-handler-unofficial-proof-of-concept" target="_blank">Panda Crazy</a>.)</div> | |
| <style> | |
| .hn2-title { | |
| font-size: 2.0em; | |
| font-weight: bold; | |
| } | |
| .hn2-subtitle { | |
| font-size: 1.0em; | |
| font-style: italic; | |
| } | |
| .hn2-subtitle-tip { | |
| font-size: 0.8em; | |
| font-style: italic; | |
| margin-bottom: 15px; | |
| } | |
| .hn2-subtitle-tip a { | |
| color: silver; | |
| } | |
| </style> | |
| `; | |
| const VERSION_NUMBER_COLOR_CHANGE_INTERVAL_MS = 333; | |
| var readyForNotifications; | |
| var processedGIDs = {}; | |
| const DELAY_BEFORE_FIRST_NOTIFICATION_MS = 1000; | |
| function startRandomVersionNumberColorChanges() { | |
| setInterval(() => { | |
| let versionNumberSpan = document.getElementById("versionNumber"); | |
| versionNumberSpan.style.color = `rgb(${Math.floor(Math.random()*255)},${Math.floor(Math.random()*255)},${Math.floor(Math.random()*255)})`; | |
| }, VERSION_NUMBER_COLOR_CHANGE_INTERVAL_MS); | |
| } | |
| function modifyDonateButtons() { | |
| let oldDonateButton = document.querySelector("a[href^='https://www.paypal.com/cgi']"); | |
| oldDonateButton.innerText = "Donate to Site Developer (kadauchi)"; | |
| oldDonateButton.classList.add("donate-button"); | |
| let newDonateButton = oldDonateButton.cloneNode(); | |
| newDonateButton.innerText = "Donate to Userscript Developer (salembeats)"; | |
| newDonateButton.href = "https://paypal.me/salembeats"; | |
| newDonateButton.classList.add("donate-button"); | |
| oldDonateButton.insertAdjacentElement("afterend", newDonateButton); | |
| } | |
| function updateHeaderAndTitle() { | |
| let headerHeading = document.querySelector("#header h3"); | |
| headerHeading.outerHTML = NEW_HEADER_HTML; | |
| document.title = "Hit Notifier 2"; | |
| } | |
| function changeSiteStyles() { | |
| document.body.style = "background: black !important;"; | |
| let allBreakElements = document.querySelectorAll("br"); | |
| allBreakElements[0].remove(); | |
| allBreakElements[1].remove(); | |
| let bottomBreakElements = document.querySelectorAll("div.container-fluid br"); | |
| bottomBreakElements[bottomBreakElements.length-1].remove(); | |
| document.querySelectorAll("div.col").forEach(columnDiv => { | |
| if(columnDiv.innerHTML === "") {columnDiv.remove();} | |
| }); | |
| let mainContentDiv = document.querySelector("div.col-10"); | |
| mainContentDiv.classList.replace("col-10", "col-12"); | |
| let donationDiv = document.querySelector("div.col-10"); | |
| donationDiv.classList.replace("col-10", "col-12"); | |
| let headerColumn = document.querySelector("#header div.col.text-center"); | |
| headerColumn.classList.replace("col", "col-12"); | |
| document.body.insertAdjacentHTML("beforeend", ` | |
| <style> | |
| a.pay-me-link { | |
| color: gold; | |
| } | |
| span.badge.badge-default { | |
| display: none; | |
| } | |
| span.post-title-bar { | |
| font-size: 0.85em; | |
| font-weight: bold; | |
| color: white; | |
| margin-bottom: 5px; | |
| } | |
| .col-12.card { | |
| background: black !important; | |
| padding: 0px !important; | |
| } | |
| #header { | |
| margin-bottom: 0px !important; | |
| } | |
| .donate-button { | |
| margin: 5px 10px 0px 10px; | |
| } | |
| .card-block { | |
| padding: 3px; | |
| } | |
| a[href='#previous'] b::after { | |
| content: " (All Found HITs)"; | |
| } | |
| ul.nav.nav-tabs.nav-justified li:first-of-type { | |
| display: none; | |
| } | |
| a.nav-link { | |
| color: silver; | |
| } | |
| .col { | |
| padding: 0px !important; | |
| } | |
| #users { | |
| position: fixed; | |
| padding: 5px; | |
| right: 5px; | |
| top: 5px; | |
| font-size: 12px; | |
| display: inline-block; | |
| background: black; | |
| z-index: ${Number.MAX_SAFE_INTEGER}; | |
| pointer-events: none; | |
| opacity: 0.6; | |
| } | |
| div.collapse.accordion.bg-faded table { | |
| margin: auto !important; | |
| min-width: 50% !important; | |
| max-width: 60% !important; | |
| } | |
| #previous div.card { | |
| background: #565656 !important; | |
| } | |
| #options div.card { | |
| background: rgb(247,247,247) !important; | |
| } | |
| div.enhanced-post-contents { | |
| } | |
| span.big-slash { | |
| font-size: 1.5em; | |
| font-weight: bold; | |
| line-height: 0.9; | |
| } | |
| span.hit-reward { | |
| color: green; | |
| font-weight: bold; | |
| font-size: 2.0em; | |
| } | |
| div.description-line { | |
| font-size: 0.8em; | |
| } | |
| span.xhr-response { | |
| font-size: 0.65em; | |
| } | |
| span.xhr-response-value { | |
| font-weight: bold; | |
| } | |
| span.xhr-response-value.xhr-success { | |
| color: green !important; | |
| } | |
| span.xhr-response-value.xhr-failure { | |
| color: red !important; | |
| } | |
| span.gid-section { | |
| } | |
| span.gid-value { | |
| color: gray; | |
| } | |
| span.rid-value { | |
| color: gray; | |
| } | |
| span.title-line-contents { | |
| font-size: 1.5em; | |
| font-weight: bold; | |
| } | |
| span.requester-line-contents { | |
| font-size: 1.2em; | |
| } | |
| span.description-line-contents { | |
| font-size: 1.0em; | |
| font-style: oblique; | |
| } | |
| span.batch-or-survey-section { | |
| color: purple; | |
| } | |
| #accordion > .card { | |
| cursor: auto !important; | |
| } | |
| button { | |
| cursor: pointer !important; | |
| } | |
| .card { | |
| pointer-events: none; | |
| } | |
| .card a { | |
| pointer-events: auto !important; | |
| } | |
| .card button { | |
| pointer-events: auto !important; | |
| } | |
| .card label { | |
| pointer-events: auto !important; | |
| } | |
| .card input { | |
| pointer-events: auto !important; | |
| } | |
| .card select { | |
| pointer-events: auto !important; | |
| } | |
| </style> | |
| `); | |
| } | |
| function gotoFullList() { | |
| document.querySelector("a.nav-link[href='#previous']").click(); | |
| } | |
| function markAllAsViewed() { | |
| document.getElementById("all-viewed").click(); | |
| } | |
| function modifyPostSurroundings( post ) { | |
| let oldPostTitleBar = post.parentElement.querySelector("[data-parent='#accordion']"); | |
| if(!oldPostTitleBar) {return;} | |
| oldPostTitleBar.outerHTML = `<span class="post-title-bar">${oldPostTitleBar.innerHTML}</span>`; | |
| let postTitleBar = post.parentElement.querySelector("span.post-title-bar"); | |
| postTitleBar.insertAdjacentHTML("beforeend", ` | |
| <span> | |
| <span class="just-here-to-make-parent-element-chains-consistent"><button class="hit-interface-button hit-source btn btn-xs btn-sm btn-default">Site</button></span> | |
| <span style="float: right;"> | |
| <button class="hit-interface-button hit-info btn btn-xs btn-sm btn-default">?</button> | |
| <button class="hit-interface-button hit-minimize btn btn-xs btn-sm btn-default">_</button> | |
| <button class="hit-interface-button hit-close btn btn-xs btn-sm btn-danger">X</button> | |
| </span> | |
| </span> | |
| `); | |
| } | |
| function replacePostContents( post ) { | |
| let parser = new MturkExportParser(post); | |
| let results = parser.getAllResults(); | |
| if(results.gid) { | |
| if(processedGIDs[results.gid]) { | |
| } | |
| else { | |
| notifyIfReady(post, parser); | |
| processedGIDs[results.gid] = true; | |
| } | |
| } | |
| let oldTable = post.querySelector("table"); | |
| if(!oldTable) {return;} | |
| oldTable.outerHTML = ` | |
| <div class="enhanced-post-contents" data-old-html-contents="${encodeURIComponent(oldTable.innerHTML)}"> | |
| <div class="title-line"><strong>Title:</strong> <span class="title-line-contents">${results.hitTitle || "No Title Found"}</span> <span class="gid-section">[<span class="gid-value">${results.gid}</span>]</span></div> | |
| <div class="requester-line"><strong>Requester:</strong> <span class="requester-line-contents">${results.requesterName || "No Requester Name Found"}</span>${(results.rid ? ` <span class="rid-section">[<span class="rid-value">${results.rid}<span>]<span>`: "")}</div> | |
| <div class="value-line"><strong>Value:</strong> <span class="hit-reward">$${Number(results.hitValue).toLocaleString("US", {minimumFractionDigits: 2})}</span> <span class="batch-or-survey-section">${(results.batchOrSurvey ? results.batchOrSurvey : "")}</span></div> | |
| <div class="description-line"><strong>Description:</strong> <span class="description-line-contents">${results.hitDescription || "No Description Found"}</span></div> | |
| <div class="links-line"><strong>Links:</strong> <a href="${parser.webPandaURL()}" target="_blank" title="Shift+Click to force to run only once. Ctrl+Click to open in background tab.">web+panda://</a> <span class="big-slash">/</span> <a href="https://worker.mturk.com/projects/${results.gid}/tasks/accept_random" target="_blank">Accept</a> <span class="big-slash">/</span> <a class="xhr-accept" href="javascript:void(0)" data-gid="${results.gid}">XHR</a> <span class="big-slash">/</span> <a href="https://worker.mturk.com/projects/${results.gid}/tasks?ref=w_pl_prvw" target="_blank">Preview</a></div> | |
| <div class="status-text-for-gid-${results.gid}"></div> | |
| </div> | |
| `; | |
| } | |
| function showPostIfHidden( post ) { | |
| post.style.display = "block"; | |
| } | |
| function openWebPandaInNewWindow(webPandaURL) { | |
| const WINDOW_NAME_NOT_NEEDED = undefined; | |
| window.open( webPandaURL, WINDOW_NAME_NOT_NEEDED, | |
| `left=0,top=0,width=${WEB_PANDA_WINDOW_WIDTH},height=${WEB_PANDA_WINDOW_HEIGHT}` ); | |
| } | |
| function handleNotificationClicked(webPandaURL, e) { | |
| if(e === true) { | |
| if(SHOULD_OPEN_NOTIFICATION_HITS_IN_BACKGROUND_TAB) { | |
| GM_openInTab( webPandaURL , SHOULD_OPEN_NOTIFICATION_HITS_IN_BACKGROUND_TAB); | |
| } | |
| else { | |
| openWebPandaInNewWindow(webPandaURL); | |
| } | |
| } | |
| } | |
| function notifyIfReady( post, parser ) { | |
| var results = parser.getAllResults(); | |
| var webPandaURL = parser.webPandaURL(); | |
| var notificationClickHandler = handleNotificationClicked.bind(unsafeWindow, webPandaURL); | |
| if(readyForNotifications) { | |
| GM_notification({ | |
| title: `Hit Notifier 2: New HIT Found!`, | |
| text: `Title: ${results.hitTitle}\n` + | |
| `Requester: ${results.requesterName} [${results.rid}]\n` + | |
| `Description: ${results.hitDescription}\n` + | |
| `Value: $${Number(results.hitValue).toLocaleString("US", {minimumFractionDigits: 2})}`, | |
| timeout: 0, | |
| ondone: notificationClickHandler | |
| }); | |
| } | |
| } | |
| function processPost( post ) { | |
| showPostIfHidden(post); | |
| replacePostContents(post); | |
| modifyPostSurroundings(post); | |
| } | |
| function toggleMinimizePost( post ) { | |
| if(post.style.display.includes("none")) { | |
| post.style.display = "block"; | |
| return false; | |
| } | |
| else { | |
| post.style.display = "none"; | |
| return true; | |
| } | |
| } | |
| function removePost( post ) { // Returns whether or not action was taken to remove an existing post. | |
| if(post) { | |
| post | |
| .parentElement | |
| .remove(); | |
| return true; | |
| } | |
| else {return false;} | |
| } | |
| function processAllPosts() { | |
| let allWorkElements = document.querySelectorAll("div.accordion.bg-faded"); | |
| allWorkElements.forEach( post => { | |
| processPost(post); | |
| }); | |
| } | |
| function isALink(element) { | |
| return element && element.tagName && element.tagName.toLowerCase() === "a"; | |
| } | |
| function getLastPageURLOfTodaysTurkerHubDailyThread() { | |
| return "https://turkerhub.com/forums/daily-mturk-hits-threads.2/"; | |
| } | |
| function getLastPageURLOfTodaysMTCDailyThread() { | |
| return "https://www.mturkcrowd.com/forums/daily-work-threads.4/"; | |
| } | |
| function getLastPageURLOfTodaysMturkForumDailyThread() { | |
| return "https://mturkforum.com/index.php?forums/great-hits.43/"; | |
| } | |
| var handlers = { | |
| handleNavLinkClick: function(e) { | |
| processAllPosts(); | |
| }, | |
| handleWorkCapsuleClick: function(e) { | |
| }, | |
| handleXHRClick: function(e) { | |
| acceptHitByGID(e.target.dataset.gid); | |
| }, | |
| handleWebPandaClick: function(e) { | |
| e.preventDefault(); | |
| let openInBackgroundTab = Boolean(e.ctrlKey); | |
| let insertOnceMetadata = Boolean(e.shiftKey); | |
| const parsedInitialURL = new URL(e.target.href); | |
| let finalWebPandaURL = e.target.href; | |
| finalWebPandaURL = finalWebPandaURL + ( !parsedInitialURL.searchParams.get("once") && insertOnceMetadata ? "&once=true" : "" ); | |
| if(openInBackgroundTab) { | |
| GM_openInTab( finalWebPandaURL, openInBackgroundTab); | |
| } | |
| else { | |
| openWebPandaInNewWindow(finalWebPandaURL); | |
| } | |
| }, | |
| handleHITInterfaceButtonClick: function(e) { | |
| let post = | |
| e.target | |
| .parentElement | |
| .parentElement | |
| .parentElement | |
| .parentElement | |
| .querySelector("div.accordion.bg-faded"); | |
| let titleBar = post.parentElement.querySelector("span.post-title-bar"); | |
| if(e.target.className.includes("hit-info")) { | |
| } | |
| else if(e.target.className.includes("hit-minimize")) { | |
| toggleMinimizePost(post); | |
| } | |
| else if(e.target.className.includes("hit-close")) { | |
| removePost(post); | |
| } | |
| else if(e.target.className.includes("hit-source")) { | |
| let now = new Date(); | |
| let month = now.getMonth(); | |
| let dayOfMonth = now.getDate(); | |
| let titleBarTextLowercase = titleBar.innerText.toLowerCase(); | |
| let siteName = titleBarTextLowercase.match(/on ([A-Za-z/]+)/)[1]; | |
| let openList = { | |
| "turkerhub": getLastPageURLOfTodaysTurkerHubDailyThread(), | |
| "mturkcrowd": getLastPageURLOfTodaysMTCDailyThread(), | |
| "mturkforum": getLastPageURLOfTodaysMturkForumDailyThread(), | |
| "/r/hitsworthturkingfor/": "https://www.reddit.com/r/HITsWorthTurkingFor/" | |
| }; | |
| window.open( openList[siteName] ); | |
| } | |
| } | |
| }; | |
| function addEventListeners() { | |
| document.addEventListener("click", e => { | |
| if(e.target.dataset && e.target.dataset.parent && e.target.dataset.parent === "#accordion") { | |
| handlers.handleWorkCapsuleClick(e); | |
| } | |
| else if(e.target.className && e.target.className.includes("nav-link")) { | |
| handlers.handleNavLinkClick(e); | |
| } | |
| else if(e.target.dataset && e.target.dataset.gid) { | |
| handlers.handleXHRClick(e); | |
| } | |
| else if(e.target.tagName && e.target.tagName.toLowerCase() === "a") { | |
| if(e.target.href.startsWith("web+panda://")) { | |
| handlers.handleWebPandaClick(e); | |
| } | |
| } | |
| else if(e.target.tagName && e.target.tagName.toLowerCase() === "button" && e.target.className.includes("hit-interface-button")) { | |
| handlers.handleHITInterfaceButtonClick(e); | |
| } | |
| }); | |
| } | |
| var mutationObserver = new MutationObserver(mutations => { | |
| for(let mutation of mutations) { | |
| if(mutation.addedNodes.length > 0) { | |
| for(let addedNode of mutation.addedNodes) { | |
| if(addedNode.tagName && | |
| addedNode.tagName.toLowerCase() === "div" && | |
| addedNode.classList && | |
| addedNode.className.includes("card")) { | |
| let addedPost = addedNode.querySelector("div.accordion.bg-faded"); | |
| processPost(addedPost); | |
| } | |
| } | |
| } | |
| if(mutation.type === "attributes") { | |
| // Not needed... yet. | |
| } | |
| } | |
| }); | |
| function startMutationObserver() { | |
| mutationObserver.observe(document.body, {childList: true, subtree: true, attributes: true}); | |
| } | |
| function displayXHRStatusHTMLByGID(statusHTML, gid) { | |
| document.querySelectorAll(`div[class*="status-text-for-gid-${gid}"]`).forEach(matchingStatusDiv => { | |
| matchingStatusDiv.innerHTML = statusHTML; | |
| }); | |
| } | |
| function logXHRResponse(logMessage, gid, isSuccessful) { | |
| displayXHRStatusHTMLByGID(`<span class="xhr-response">Last XHR Attempt (${new Date().toLocaleString()}): <span class="xhr-response-value${(isSuccessful ? " xhr-success" : " xhr-failure")}">${logMessage}</span></span>`, gid); | |
| } | |
| function handleLoggedOutResponse(response, parsedResponse, gid) { | |
| logXHRResponse("You're logged out of mTurk!", gid, false); | |
| } | |
| function handleCaptchaResponse(response, parsedResponse, gid) { | |
| logXHRResponse("Encountered a CAPTCHA!", gid, false); | |
| } | |
| function handleQueueFull(response, parsedResponse, gid) { | |
| logXHRResponse("Your queue is already full!", gid, false); | |
| } | |
| function handlePRE(response, parsedResponse, gid) { | |
| logXHRResponse("Too fast! Encountered a Page Request Error (PRE)!", gid, false); | |
| } | |
| function handleNoHITsAvailable(response, parsedResponse, gid) { | |
| logXHRResponse("No HITs available. Try again or move on.", gid, false); | |
| } | |
| function handleXHRHitAccepted(response, parsedResponse, gid) { | |
| logXHRResponse("Caught one!", gid, true); | |
| } | |
| function handleNotQualified(response, parsedResponse, gid) { | |
| logXHRResponse("You don't have the right qualifications to accept this HIT!", gid, false); | |
| } | |
| function handleXHRHitNotAccepted(response, parsedResponse, gid) { | |
| if(response.statusText === "Too Many Requests") { | |
| handlePRE(response, parsedResponse, gid); | |
| } | |
| else if(parsedResponse) { | |
| if(parsedResponse.message.startsWith("You have accepted the maximum number of HITs allowed at one time.")) { | |
| handleQueueFull(response, responseText, gid); | |
| } | |
| if(parsedResponse.message === "There are no more of these HITs available.") { | |
| handleNoHITsAvailable(response, parsedResponse, gid); | |
| } | |
| if(parsedResponse.message.startsWith("This Requester has specified Qualifications for this HIT.")) { | |
| handleNotQualified(response, parsedResponse, gid); | |
| } | |
| } | |
| } | |
| function handleXHRResponse(gid, response) { | |
| let parsedResponse; | |
| try { | |
| parsedResponse = JSON.parse(response.responseText); | |
| } catch(err) { | |
| handleLoggedOutResponse(response, parsedResponse, gid); | |
| } | |
| if(!parsedResponse) {return;} | |
| if(response.status === 200) { | |
| if(Boolean(parsedResponse.task_id)) { | |
| handleXHRHitAccepted(response, parsedResponse, gid); | |
| } | |
| else { | |
| handleCaptchaResponse(response, parsedResponse, gid); | |
| } | |
| } | |
| else { | |
| handleXHRHitNotAccepted(response, parsedResponse, gid); | |
| } | |
| } | |
| function acceptHitByGID(gid) { | |
| GM_xmlhttpRequest({ | |
| method: "GET", | |
| url: `https://worker.mturk.com/projects/${gid}/tasks/accept_random`, | |
| headers: { | |
| Accept: "application/json" | |
| }, | |
| onload: handleXHRResponse.bind(window, gid) | |
| }); | |
| } | |
| function changeFavicon(src) { | |
| var link = document.createElement('link'), | |
| oldLink = document.getElementById('dynamic-favicon'); | |
| link.id = 'dynamic-favicon'; | |
| link.rel = 'shortcut icon'; | |
| link.href = src; | |
| if (oldLink) { | |
| document.head.removeChild(oldLink); | |
| } | |
| document.head.appendChild(link); | |
| } | |
| function main() { | |
| changeFavicon(SALEM_BEATS_ICON_URL); | |
| updateHeaderAndTitle(); | |
| startRandomVersionNumberColorChanges(); | |
| changeSiteStyles(); | |
| gotoFullList(); | |
| startMutationObserver(); | |
| addEventListeners(); | |
| processAllPosts(); | |
| markAllAsViewed(); | |
| modifyDonateButtons(); | |
| setTimeout(() => readyForNotifications = true, DELAY_BEFORE_FIRST_NOTIFICATION_MS); | |
| } | |
| main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment