NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Muffin for Callyouback // @namespace ninco // @version 1.2 // @description Add profile reference link on muffin.net // @author you // @copyright 2020, ninco (https://openuserjs.org/users/ninco) // @license MIT // @downloadURL https://openuserjs.org/install/ninco/Muffin_for_Callyouback.user.js // @updateURL https://openuserjs.org/meta/ninco/Muffin_for_Callyouback.meta.js // @supportURL https://openuserjs.org/scripts/ninco/Muffin_for_Callyouback/issue/new // @require https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.18.2/babel.js // @require https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.16.0/polyfill.js // @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js // @require https://raw.githubusercontent.com/bitlyfied/js-image-similarity/master/simi.js // @match naberu.com/* // @grant GM_xmlhttpRequest // @connect keksik.net // @run-at document-idle // ==/UserScript== /* globals $ */ /////// CONFIG ////// let DEBUG = false; let muffinCorsFetchFlag = true; let corsInterval = 2700; // delay between CORS requests in ms (to avoid captcha) let limitCards = 5*DEBUG; // total 50 on page if (!DEBUG) { console.log = function () {}; } ////// MAIN ////// let pathname = window.location.pathname; // card profile (URL ends with digits) if (/^\d+$/.test(pathname.split("/").slice(-1)[0])) { console.log("Parsing number in profle"); let phoneNumber = $(".mob-hidden.pc-hidden").text().trim(); $(".phone-item-large").prepend(createMuffinAnchorTag(phoneNumber)); setTimeout(() => {$("#show_number").click()}, 1000); } // search result list (OUTDATED) else if (false && pathname === "/" || pathname.split("/")[1] === "search") { console.log("Parsing search result for profiles"); let lastCorsTimestamp = 0; let cardHeaders = $(".card-block .naberu-item-title"); if (limitCards) { cardHeaders.splice(limitCards); } console.log("Cards URLs parsed: %s", cardHeaders.length); cardHeaders.each((i, headerEl) => { let profileQuickViewUrl = $(headerEl).find(".link-default").data("href"); // request each card $.ajax({ url: profileQuickViewUrl, cache: false, timeout: 2000, dataType: "html", _header: $(headerEl), error: ajaxErrorHandler(), success: function (htmlData, status, xhr) { let phoneNumber = $(htmlData).find("a.copy-phone").text().slice(-10); if (!isPhoneNumberValid(phoneNumber)) { console.error("Invalid %s: %s %s", i+1, phoneNumber, this.url); return false; } this._header.append(createStatusLabelTag(phoneNumber), " ", createMuffinAnchorTag(phoneNumber)); console.log("%s: %s %s", i+1, phoneNumber, this.url); // muffin CORS if (muffinCorsFetchFlag) { setTimeout(() => { // console.log("%s CORS for %s", new Date(), phoneNumber); muffinCors(phoneNumber, {header: this._header, requestTs: nowStr()}); }, lastCorsTimestamp+=corsInterval); } } }); }); } else { console.log("Unhandled URL: %s", window.location.href); } /////// UTILITIES ///// function getMuffinUrl(phone) { return "https://keksik.net/phone/" + phone; } function createMuffinAnchorTag(phone) { return $("<a>").attr({ href: getMuffinUrl(phone), target: "_blank" }).text("🧁"); } function createStatusLabelTag(msg) { return $("<sub>").css({ "font-weight": 100 }).text(msg); } function isPhoneNumberValid(numberStr) { return /^0\d{9}$/.test(numberStr); } // 0xx44442211 function nowStr() { let now = new Date(); return `${(now.getMinutes()<10?'0':'') + now.getMinutes()}:${(now.getSeconds()<10?'0':'') + now.getSeconds()}`; } function muffinCors(phone, context) { GM_xmlhttpRequest({ // ajax CORS method: "GET", url: getMuffinUrl(phone), context: context, onload: function(response) { let msg = null; console.log("%s muffin response: %s %s [%s]", this.context.requestTs, response.status, response.statusText, response.finalUrl); if (response.finalUrl.includes("recaptcha")) { msg = "🤖"; } else if (response.status === 200) { msg = "✓"; let images = $(response.responseText).find(".container-items img"); let image_urls = images.map((_, e) => e.src).toArray(); let count = Counter(image_urls); let biggest_count = count[Object.keys(count)[0]]; if (biggest_count > 5) msg += " same"; else msg += " diff"; //simi.hash(image) } else { msg = "✗"; } response.context.header.append(msg); }, onerror: function(response) { console.error("Muffin CORS error %s %s: %s", response.status, response.statusText, this.url); } }); }; function ajaxErrorHandler() { return function(xhr, status, exception) { console.error("Request failed with '%s' (%s): %s %s %s", status, exception, this.method, this.url, this.data ? `, data ${this.data}` : ""); let errorIcon = null; switch (status) { case "error": errorIcon = "🛑"; break; case "timeout": errorIcon = "⏳"; break; default: errorIcon = "❌"; }; this._header.append(errorIcon); if (DEBUG) this._header.append(" ", createStatusLabelTag(exception)); }; }; // download and render image in dom function getImage(url){ return new Promise(function(resolve, reject){ var img = new Image() img.onload = function(){ resolve(url) } img.onerror = function(){ reject(url) } img.src = url }) }; // count and sort the same elements in array => {num: value} function Counter(array) { var count = {}; array.forEach(val => { count[val] = (count[val] || 0) + 1; }); return sortObject(count); }; // sort object {num: value} function sortObject(obj) { var sortable = []; for (var i in obj) sortable.push([i, obj[i]]); // convert {num: value} => [num, value] return sortable.sort((a, b) => b[1] - a[1]).reduce((acc, cv) => {acc[cv[0]] = cv[1]; return acc;}, {}); // sort and convert back to {num: value} };