Skip to content

Instantly share code, notes, and snippets.

@Kenya-West
Last active September 19, 2022 13:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kenya-West/55bc1a455d4066dfebadb2700a5688a1 to your computer and use it in GitHub Desktop.
Save Kenya-West/55bc1a455d4066dfebadb2700a5688a1 to your computer and use it in GitHub Desktop.
2GIS-phone-number-copier

Description

A js file for Tampermonkey that allows you to copy any russian mobile phone number in 2GIS.ru website redirect to the new item page. This project is a script for 2GIS website.

How to contribute

Clone this project on GitHub and start your changes. If you want to use the script from source code, then create new script in Tampermonkey extension and put source code in opened window.

How to use this script?

You need Tampermonkey extension installed on your browser to use this style.

P. S. Author is not affiliated in any kind of mentioned organizations.

// ==UserScript==
// @name 2GIS phone number copier
// @namespace http://2gis.ru/
// @license MIT
// @version 0.5.0
// @description 2GIS mobile phone number copier allow you to copy any Russian mobile phone number to your device"s clipboard
// @author Kenya-West
// @include https://2gis.ru/*
// @grant GM_setClipboard
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
// store url on load
let currentPage = location.href;
let recordList = getFromGM("recordList");
let tableMode = recordList.length ? true : false;
urlCheckCounter = 0;
// listen for changes
setInterval(function()
{
if (currentPage !== location.href || urlCheckCounter === 0)
{
// page has changed, set new page as "current"
currentPage = location.href;
main();
}
urlCheckCounter++;
}, 175);
function main() {
// Grand-parent for parentElems
const phoneContainer = document.querySelector("._172gbf8:has(>._49kxlr>._b0ke8)");
// Parents for hrefs
const phoneContainerChild = document.querySelector("._172gbf8 ._49kxlr:has(>._b0ke8)");
//
// INIT FUNCTION SET
//
if (expandTels()) {
placeTableMode();
addListenersOnRender();
setTimeout(() => {
placeIcons();
}, 100);
// placeIcons(elemsMedia, parentElemsMedia, parentHrefsMedia, numberLinkMedia);
}
/**
* Expands "Показать телефоны" button
*
* The top-level checking function
*
* @returns `boolean`
*/
function expandTels() {
const event = new Event("click", {bubbles: true})
const expandElem = document.querySelector("._1ns0i7c");
if (expandElem) {
expandElem.dispatchEvent(event);
return true;
} else {
return false;
}
}
//
// PLACE FUNCTION SET
//
function placeIcons() {
// Parents for hrefs
const parents = document.querySelectorAll("._49kxlr > ._b0ke8");
// Parent elements a[href] that are parents for elems
const hrefs = document.querySelectorAll("._49kxlr > ._b0ke8 > a");
// Number link like `tel:` containing number. Usually it"s the hrefs[index]
const telLink = document.querySelectorAll("._49kxlr ._b0ke8 > a");
// Elements that contain phone number in innerText
const elems = document.querySelectorAll("._49kxlr > ._b0ke8 > a");
elems.forEach((element, index) => {
let whatsappLink = preparePhoneNumber(telLink[index].href, index);
let phoneLink = preparePhoneNumber(telLink[index].href, index);
let phoneNumber = preparePhoneNumber(telLink[index].href, index);
if (whatsappLink && !parents[index].getAttribute("whatsapp-linked")) {
let whatsapp = "https://wa.me/" + whatsappLink;
let button = prepareWhatsappButton(whatsapp, index);
parents[index].insertBefore(button, hrefs[index]);
parents[index].setAttribute("whatsapp-linked", true);
storePhoneOrLink("whatsappLink", whatsapp);
}
if (phoneLink) {
let phone = "tel:+" + phoneLink;
}
if (phoneNumber && !parents[index].getAttribute("phone-linked")) {
let phone = `+${phoneNumber[0]} ${phoneNumber[1]}${phoneNumber[2]}${phoneNumber[3]} ${phoneNumber[4]}${phoneNumber[5]}${phoneNumber[6]}-${phoneNumber[7]}${phoneNumber[8]}-${phoneNumber[9]}${phoneNumber[10]}`
let button = preparePhoneButton(phone, index);
parents[index].insertBefore(button, hrefs[index]);
parents[index].setAttribute("phone-linked", true);
storePhoneOrLink("phone", phone);
}
updateState();
});
}
function placeCounter() {
let counter = prepareCounter();
if(counter) {
// let element = prepareCounter();
phoneContainerChild.appendChild(counter);
}
updateState();
}
function placeTableMode() {
let tableModeButton = prepareTableModeButton();
phoneContainerChild.appendChild(tableModeButton);
tableModeCheckbox.checked = tableMode;
}
function tableAddRemoveButtonManager(value) {
if (value) {
placeTableAddRemoveButton();
} else {
removeTableAddRemoveButton();
}
}
function placeTableAddRemoveButton() {
const host = phoneContainerChild.querySelector("#tableModeAddRemoveButtonHost");
if (!host) {
let tableModeAddRemoveButton = prepareTableModeAddRemoveButton();
phoneContainerChild.appendChild(tableModeAddRemoveButton);
phoneContainer.querySelector("#tableModeAddRemoveButton").addEventListener("click", (element) => {
pushOrRemoveRecord();
})
}
}
function removeTableAddRemoveButton() {
const elem = phoneContainerChild.querySelector("#tableModeAddRemoveButtonHost");
if (elem) {
elem.parentNode.removeChild(elem)
}
}
function tableCopyButtonManager(value) {
if (value) {
placeTableCopyButton();
} else {
removeTableCopyButton();
}
}
function placeTableCopyButton() {
const host = phoneContainerChild.querySelector("#tableCopyButtonHost");
if (!host) {
let tableCopyButton = prepareTableCopyButton();
phoneContainerChild.appendChild(tableCopyButton);
phoneContainer.querySelector("#tableCopyButton").addEventListener("click", (element) => {
copyTable();
})
}
}
function removeTableCopyButton() {
const elem = phoneContainerChild.querySelector("#tableCopyButtonHost");
if (elem) {
elem.parentNode.removeChild(elem)
}
}
function placeTable() {
const data = recordList;
// {
// id: firmId ?? recordList.length,
// name: firmName,
// shortUrl: value.shorturl ?? "",
// fullUrl: location.href,
// phones: [...getPhonesOrLinks("phone")],
// whatsappLinks: [...getPhonesOrLinks("whatsappLink")]
// }
phoneContainer.insertAdjacentHTML(
`beforeend`,
`
<table id="contactsTable" bordercolor="gray" border="2">
<thead>
<tr>
<th>📄 Название</th>
<th>📞 Номер</th>
<th>🔗 Ссылка</th>
<th>💭 Комментарий</th>
</tr>
</thead>
<tbody>
${[...data]
.map(
(_, i) => `<tr>${[...Array(4)]
.map(
(_, j) =>
{
if (j===1) { return `<td><a href="${data[i].shortUrl ?? data[i].fullUrl}">${data[i].name}</a></td>`}
if (j===2) { return `<td>${data[i].phones.map((_, k) => {
return `${_}<br>`
}).join("")}</td>`}
if (j===3) { return `<td>${data[i].whatsappLinks.map((_, k) => {
return `<a href="${_}">${_}</a><br>`
}).join("")}</td>`}
if (j===4) { return `<td></td>`}
}
)
.join("")}
</tr>`
)
.join("")}
</tbody>
</table>
`
);
}
function removeTable() {
const elem = phoneContainer.querySelector("#contactsTable");
elem.parentNode.removeChild(elem);
}
//
// PREPARE FUNCTION SET
//
function preparePhoneNumber(phone, index) {
const regex = /79\d+/;
if (regex.test(phone)) {
let result = regex.exec(phone);
if (result[0].length < 11) {
return null;
}
return result[0]
} else { return null }
}
function prepareWhatsappButton(whatsappLink, index) {
const button = document.createElement("img");
button.id = "whatsappLink" + index;
button.src = "https://upload.wikimedia.org/wikipedia/commons/1/19/WhatsApp_logo-color-vertical.svg";
button.style.width = "18px";
button.style.height = "18px";
button.style.display = "inline-block";
button.style.cursor = "pointer";
button.style.marginRight = "5px";
button.setAttribute("whatsapp-link", whatsappLink);
button.addEventListener("click", (element) => {
if (element.target && element.target.id === button.id) {
GM_setClipboard(whatsappLink);
element.toElement.style.backgroundImage = "green";
window.setTimeout(() => {
element.toElement.style.backgroundImage = "none";
}, 1000);
}
});
return button;
}
function preparePhoneButton(phone, index) {
const button = document.createElement("img");
button.id = "phone" + index;
button.src = "https://upload.wikimedia.org/wikipedia/commons/8/83/Circle-icons-phone.svg";
button.style.width = "18px";
button.style.height = "18px";
button.style.display = "inline-block";
button.style.cursor = "pointer";
button.style.marginRight = "5px";
button.setAttribute("phone", phone);
button.style.tra
button.addEventListener("click", (element) => {
if (element.target && element.target.id === button.id) {
GM_setClipboard(phone);
element.toElement.style.backgroundImage = "green";
window.setTimeout(() => {
element.toElement.style.backgroundImage = "none";
}, 1000);
}
});
return button;
}
function prepareTableModeButton() {
let innerHTML = `<ul>
<li class="_gyromm" id="tableMode">
<label class="_vvxysz1" title="Режим таблицы"
><div class="_okzfjf">
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="black">
<path d="M19 18V6H5v15a5 5 0 0 0 5 5h12a5 5 0 0 0 5-5v-3zm-6 3a3 3 0 0 1-6 0V8h10v10h-4zm12 0a3 3 0 0 1-3 3h-8a5 5 0 0 0 1-3v-1h10z"></path>
<path d="M9 10h6v2H9zM9 14h4v2H9z"></path>
</svg>
</div>
<input id="tableModeCheckbox" class="_1u9fru1" type="checkbox" value=0 /><span
class="_1iurgbx"
>Режим таблицы</span
></label
>
</li>
</ul>
`
const button = document.createElement("div");
button.classList.add("_b0ke8");
button.innerHTML = innerHTML;
return button;
}
function prepareTableModeAddRemoveButton() {
let innerHTML = `<div class="_rtsy3" id="tableModeAddRemoveButtonHost" >
<div class="_jro6t0">
<div class="_11pijuk">
<button id="tableModeAddRemoveButton" class="_1uwefnfu" type="button">Добавить</button>
</div>
</div>
</div>
`
const button = document.createElement("div");
button.innerHTML = innerHTML;
return button;
}
function prepareTableCopyButton() {
let innerHTML = `<div class="_rtsy3" id="tableCopyButtonHost" >
<div class="_jro6t0">
<div class="_11pijuk">
<button id="tableCopyButton" class="_1uwefnfu" style="background-color: #f2f2f2;color: black;margin-top: 5px;" type="button">Копировать</button>
</div>
</div>
</div>
`
const button = document.createElement("div");
button.innerHTML = innerHTML;
return button;
}
function prepareCounter() {
const counter = document.createElement("div");
counter.classList.add("_b0ke8");
counter.setAttribute("id", "recordListCounter");
return counter;
}
//
// LISTENERS AND SERVICES
//
function updateState() {
updateCounter();
changeTableModeAddRemoveButtonState(phoneContainerChild.querySelector("#tableModeAddRemoveButton"));
storeValue("recordList", recordList);
tableModeValueChangeHandler(tableMode);
tableAddRemoveButtonManager(tableMode);
tableCopyButtonManager(recordList.length > 0);
}
function updateCounter() {
const counter = phoneContainer.querySelector("#recordListCounter");
if (counter) {
counter.innerText = `Количество материалов: ${recordList.length}`;
if (!tableMode) {
counter.parentNode.removeChild(counter)
}
} else if (!counter && tableMode) {
placeCounter();
}
}
function changeTableModeAddRemoveButtonState(elem) {
const firmId = /firm\/(\d+)/gi.exec(location.href)[1];
if (phoneContainerChild.querySelector("#tableModeAddRemoveButtonHost")) {
if (recordList.find((record) => record.id === firmId)) {
elem.textContent = "Удалить";
elem.style.backgroundColor = "#299400";
elem.style.color = "white";
} else {
elem.textContent = "Добавить";
elem.style.backgroundColor = "#f2f2f2";
elem.style.color = "black";
}
}
}
function addListenersOnRender() {
const tableModeHost = phoneContainer.querySelector("#tableMode");
const tableModeCheckbox = phoneContainer.querySelector("#tableModeCheckbox");
if (tableModeHost) {
tableModeHost.addEventListener("click", (element) => {
tableModeCheckbox.checked = !tableModeCheckbox.checked;
tableMode = tableModeCheckbox.checked;
updateState();
});
}
}
function tableModeValueChangeHandler(checked) {
// change view
const host = phoneContainer.querySelector("#tableMode");
const label = host?.querySelector("._vvxysz1, ._1t53cmw5");
const icon = host?.querySelector("._okzfjf, ._1laf6tw8");
if (host && label && icon) {
if (checked) {
label.style.backgroundColor = "rgb(48, 173, 0)";
label.style.color = "white";
icon.querySelector("svg").setAttribute("fill", "white");
} else {
label.style.backgroundColor = "inherit";
label.style.color = "initial";
icon.querySelector("svg").setAttribute("fill", "back");
}
}
}
function storeValue(key, value) {
GM_setValue(JSON.stringify(key, value));
}
// DATA PARSING
const _phones = [];
const _whatsappLinks = [];
/**
* Sets phones or links to a runtime var
* @param {string} mode "phone", "whatsappLink"
* @param {string} data string value (phone or link)
*/
function storePhoneOrLink(mode, data) {
switch (mode) {
case "phone":
_phones.push(data)
break;
case "whatsappLink":
_whatsappLinks.push(data)
break;
}
}
/**
* Returns phones or links from a runtime var
* @param {string} mode "phone", "whatsappLink"
*/
function getPhonesOrLinks(mode) {
switch (mode) {
case "phone":
return _phones;
case "whatsappLink":
return _whatsappLinks;
}
}
async function fetchData() {
let response = await fetch(`https://go.2gis.com/add/?mode=json&encoded=true&url=${encodeURI(location.href)}`);
return response.json();
}
function pushOrRemoveRecord() {
const firmId = /firm\/(\d+)/gi.exec(location.href)[1];
const firmName = document.querySelector("._6htn2u ._oqoid").innerText;
// removes a record
if (recordList.find((record) => record.id === firmId)) {
recordList = recordList.filter((record) => record.id !== firmId);
}
// adds a record
else {
fetchData().then((value) => {
recordList.push({
id: firmId ?? recordList.length,
name: firmName,
shortUrl: value.shorturl ?? "",
fullUrl: location.href,
phones: [...getPhonesOrLinks("phone")],
whatsappLinks: [...getPhonesOrLinks("whatsappLink")]
});
})
.finally(() => updateState())
}
updateState();
}
function copyTable() {
placeTable();
const table = phoneContainer.querySelector("#contactsTable");
try {
// create a Range object
var range = document.createRange();
var sel = window.getSelection();
// set the Node to select the "range"
range.selectNode(table);
sel.removeAllRanges();
sel.addRange(range);
// add the Range to the set of window selections
// window.getSelection().addRange(range);
// execute 'copy', can't 'cut' in this case
document.execCommand('copy');
}
catch(e) {}
removeTable();
}
}
//
// FUNCTIONS THAT NEED GLOBAL SCOPE
//
function getFromGM(key) {
try {
return JSON.parse(GM_getValue(key));
} catch (error) {
return []
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment