Create a gist now

Instantly share code, notes, and snippets.

Google Apps Script to find mobile phone numbers meeting certain criteria (look inside "updateNumber()" and "fixNumbersFromGoogleContacts()" for details) and add a ninth digit to them. Scans all phone numbers in your Google Contacts, using official Google APIs. Also includes a simple and ugly UI for the most basic usage.
// digit9brazil.gs
// Google Apps Script
//
// Objective:
// Find mobile phone numbers meeting certain criteria (look inside "updateNumber()" and
// "fixNumbersFromGoogleContacts()" for details) and add a ninth digit to them.
//
// Live link:
// https://script.google.com/macros/s/AKfycbzWlGLgZmA0lygkwMLp3p6RrRFIbBfVdKGXaUAcJCJo59R492E/exec
//
// Source-code:
// https://gist.github.com/denilsonsa/7165746
// https://script.google.com/d/1Jd3p0m0WS9Jjg1p8ztXDflQkCHZdj6BC36c5ZedgOov3vmRBF_4_pc_Y/edit
// http://denilson.sa.nom.br/blog/2013-10-26/bulk-editing-your-google-contact-list/
//
// References:
// http://www.anatel.gov.br/Portal/verificaDocumentos/documentoVersionado.asp?numeroPublicacao=333184&documentoPath=333184.pdf
// http://www.anatel.gov.br/Portal/exibirPortalNivelDois.do?codItemCanal=1722
// http://www.anatel.gov.br/Portal/exibirPortalNivelDois.do?codItemCanal=1794
// http://www.embratel.com.br/Embratel02/cda/portal/0,2997,PO_P_11590,00.html
// https://script.google.com/
// https://developers.google.com/apps-script/reference/contacts/
// https://developers.google.com/apps-script/reference/ui/
// Updates a single phone number, returns the updated version.
// If no update is needed, returns null.
function updateNumber(oldnum) {
// DDDs com o nono dígito já adicionado:
// SP: 11, 12, 13, 14, 15, 16, 17, 18, 19
// RJ: 21, 22, 24
// ES: 27, 28
// PA: 91, 93, 94
// AM: 92, 97
// RR: 95
// AP: 96
// MA: 98, 99
// - 2015-05-31 -
// PE: 81, 87
// AL: 82
// PB: 83
// RN: 84
// CE: 85, 86
// PI: 86, 89
// - 2015-10-11 -
// MG: 31, 32, 33, 34, 35, 37, 38
// BA: 71, 73, 74, 75, 77
// SE: 79
// DDDs ainda sem o nono dígito:
// - 2016-05-29 -
// DF: 61
// GO: 62, 64, 65
// TO: 63
// MT: 66
// MS: 67
// AC: 68
// RO: 69
// - 2016-11-06 -
// PR: 41, 42, 43, 44, 45, 46
// SC: 47, 48, 49
// RS: 51, 53, 54, 55
// TODO: When changing these dates, should also update them in fixNumbersFromGoogleContacts().
var today = new Date();
var re_ddd = /^(1[1-9]|2[12478]|3[1234578]|7[134579]|8[1-9]|9[1-9])$/;
if (today.valueOf() >= (new Date(2016, 4, 29)).valueOf()) {
re_ddd = /^(1[1-9]|2[12478]|3[1234578]|6[1-9]|7[134579]|8[1-9]|9[1-9])$/;
} else if (today.valueOf() >= (new Date(2016, 10, 6)).valueOf()) {
re_ddd = /^(1[1-9]|2[12478]|3[1234578]|4[1-9]|5[1345]|6[1-9]|7[134579]|8[1-9]|9[1-9])$/;
}
var match;
// Considerando prefixos em formatos conhecidos (+55xx, 0xx, 0yyxx, 90xx, 90yyxx, xx).
// Seguidos de 7, 8, 9 (dígito de celular).
// Seguido de 7 dígitos (i.e. número de DD + 8 dígitos).
var cleaned_number = oldnum.replace(/[- .()]/g, '');
var re_format = /^(?:\+55|0|0[0-9]{2}|90|90[0-9]{2}|)([0-9]{2})[789][0-9]{7}$/;
match = re_format.exec(cleaned_number);
if (!match) {
return null;
}
var ddd = match[1];
if (!re_ddd.test(ddd)) {
return null;
}
// Separando o prefixo+DDD do número.
var re = /^\s*(.*)([789](?:[- .]*[0-9]){7}[-.]*)\s*$/;
match = re.exec(oldnum);
if (match) {
var newnum = match[1] + '9' + match[2];
return newnum;
}
return null;
}
// should_apply:
// false = dry-run, shows what will be changed, but do not change anything.
// true = for real, apply all the changes.
function fixNumbersFromGoogleContacts(should_apply) {
var msg = '';
var total_phones = 0;
var total_modified = 0;
// TODO: When changing these dates, should also update them in updateNumber().
var estados = 'AL, AM, AP, BA, CE, ES, MA, MG, PA, PB, PE, PI, RJ, RN, RR, SE, SP';
var extra_msg = 'Tente novamente a partir de 29 de maio de 2016 para considerar outros estados.\n';
var today = new Date();
if (today.valueOf() >= (new Date(2016, 4, 29)).valueOf()) {
estados = 'AC, AL, AM, AP, BA, CE, DF, ES, GO, MA, MG, MS, MT, PA, PB, PE, PI, RJ, RN, RO, RR, SE, SP, TO';
extra_msg = 'Tente novamente a partir de 6 de novembro de 2016 para considerar todos estados.\n';
} else if (today.valueOf() >= (new Date(2016, 10, 6)).valueOf()) {
estados = 'todos os estados (AC, AL, AM, AP, BA, CE, DF, ES, GO, MA, MG, MS, MT, PA, PB, PE, PI, PR, RJ, RN, RO, RR, RS, SC, SE, SP, TO)';
extra_msg = '';
}
msg += 'Procurando por telefones de ' + estados + '.\n';
msg += extra_msg;
var contacts = ContactsApp.getContacts();
for (var i = 0; i < contacts.length; i++) {
var contact = contacts[i];
var phones = contact.getPhones();
total_phones += phones.length;
for (var j = 0; j < phones.length; j++) {
var phone = phones[j];
var oldnum = phone.getPhoneNumber();
var newnum = updateNumber(oldnum);
if (newnum) {
total_modified++;
var msgline = (should_apply ? 'Alterando' : 'A ser alterado') + ': "' +
oldnum + '" para "' + newnum + '" (' + contact.getFullName() + ')';
msg += msgline + '\n';
//Logger.log(msgline);
if (should_apply) {
phone.setPhoneNumber(newnum);
}
}
}
}
if (should_apply) {
msg += 'Concluído!\n';
}
msg += total_modified + ' números ' + (should_apply ? 'modificados' : 'a serem alterados') +
', de um total de ' + total_phones + ' telefones em ' + contacts.length + ' contatos.\n';
//Logger.log('Terminado!');
return msg;
}
function newPanel(app, msg) {
var form = app.createFormPanel();
var title = app.createLabel('Adicione o dígito 9 aos contatos da sua agenda Google');
title.setStyleAttributes({
fontSize: '2em',
margin: '0 1ex'
});
var anchor = app.createAnchor('https://www.google.com/contacts/', 'https://www.google.com/contacts/');
var extra_links = app.createFlowPanel();
extra_links.add(app.createInlineLabel('Escrito por '));
extra_links.add(app.createAnchor('Denilson Sá', 'http://about.me/denilsonsa'));
extra_links.add(app.createInlineLabel(', código-fonte disponível no '));
extra_links.add(app.createAnchor('Google Apps Script', 'https://script.google.com/d/1Jd3p0m0WS9Jjg1p8ztXDflQkCHZdj6BC36c5ZedgOov3vmRBF_4_pc_Y/edit'));
extra_links.add(app.createInlineLabel(' e no '));
extra_links.add(app.createAnchor('GitHub Gist', 'https://gist.github.com/denilsonsa/7165746'));
extra_links.add(app.createInlineLabel('. '));
extra_links.add(app.createAnchor('Postagem no meu blog', 'http://denilson.sa.nom.br/blog/2013-10-26/bulk-editing-your-google-contact-list/'));
extra_links.add(app.createInlineLabel('.'));
var radioDoNotApply = app.createRadioButton('apply', 'Mostrar possíveis mudanças, porém não modificar a lista de contatos.');
radioDoNotApply.setFormValue('no');
radioDoNotApply.setValue(true);
var radioApply = app.createRadioButton('apply', 'Modificar a lista de contatos e mostrar um relatório do que foi modificado.');
radioApply.setFormValue('apply');
var text = app.createLabel(msg);
text.setId('text');
text.setStyleAttribute('white-space', 'pre-wrap');
var button = app.createSubmitButton('OK');
var panel = app.createVerticalPanel();
panel.setSpacing(5);
panel.add(title);
panel.add(anchor);
panel.add(extra_links);
panel.add(radioDoNotApply);
panel.add(radioApply);
panel.add(button);
panel.add(text);
form.add(panel);
if (app) {
app.add(form);
}
return panel;
}
function getOrPost(e, app, is_post) {
var msg = '';
if (is_post) {
var should_apply = e.parameter.apply === 'apply';
msg += fixNumbersFromGoogleContacts(should_apply);
}
// msg += Logger.getLog();
newPanel(app, msg);
}
function doGet(e) {
var app = UiApp.createApplication();
app.setTitle('Adiciona dígito 9 a celulares de RJ e ES');
getOrPost(e, app, false);
return app;
}
function doPost(e) {
var app = UiApp.getActiveApplication();
getOrPost(e, app, true);
return app;
}
////////////////////////////////////////////////////////////
// Ad-hoc unit testing:
function assertWorks(before, expected, msg, index) {
if (msg) {
msg = ' (' + msg + ')';
} else {
msg = '';
}
if (index !== undefined && index !== null) {
index = '[' + index + '] ';
} else {
index = '';
}
var actual = updateNumber(before);
if (actual === expected) {
return {
success: true,
msg: index + 'OK : "' + before + '" -> "' + expected + '"' + msg
};
} else {
return {
success: false,
msg: index + 'FAIL: "' + before + '" -> "' + actual + '" !== "' + expected + '"' + msg
};
}
}
var unitTests = [
// Gvim Tabularize plugin:
// :Tabularize /, /l0l1
{before: '0800 999 9999' , expected: null , msg: '0800'},
{before: '08009999999' , expected: null , msg: '0800'},
{before: '0800 99 9999' , expected: null , msg: '0800'},
{before: '0800999999' , expected: null , msg: '0800'},
{before: '0900 999 9999' , expected: null , msg: '0900'},
{before: '09009999999' , expected: null , msg: '0900'},
{before: '0900 99 9999' , expected: null , msg: '0900'},
{before: '0900999999' , expected: null , msg: '0900'},
{before: '2345-6789' , expected: null , msg: 'sem DDD'},
{before: '23456789' , expected: null , msg: 'sem DDD'},
{before: '9876-6789' , expected: null , msg: 'sem DDD'},
{before: '98766789' , expected: null , msg: 'sem DDD'},
{before: '99876-6789' , expected: null , msg: 'sem DDD'},
{before: '998766789' , expected: null , msg: 'sem DDD'},
{before: '+55 (21) 23.45-67.89' , expected: null , msg: '+55 21 fixo'},
{before: '+55 (21) 2345-6789' , expected: null , msg: '+55 21 fixo'},
{before: '+55 21 2345-6789' , expected: null , msg: '+55 21 fixo'},
{before: '+55 21 23456789' , expected: null , msg: '+55 21 fixo'},
{before: '+55 2123456789' , expected: null , msg: '+55 21 fixo'},
{before: '+552123456789' , expected: null , msg: '+55 21 fixo'},
{before: '(21) 2345-6789' , expected: null , msg: '21 fixo'},
{before: '21 2345-6789' , expected: null , msg: '21 fixo'},
{before: '2123456789' , expected: null , msg: '21 fixo'},
{before: '0 (21) 2345-6789' , expected: null , msg: '021 fixo'},
{before: '(021) 2345-6789' , expected: null , msg: '021 fixo'},
{before: '021 2345-6789' , expected: null , msg: '021 fixo'},
{before: '02123456789' , expected: null , msg: '021 fixo'},
{before: '+55 (21) 998.76-67.89', expected: null , msg: '+55 21 novo movel'},
{before: '+55 (21) 99876-6789' , expected: null , msg: '+55 21 novo movel'},
{before: '+55 21 99876-6789' , expected: null , msg: '+55 21 novo movel'},
{before: '+55 21 998766789' , expected: null , msg: '+55 21 novo movel'},
{before: '+55 21998766789' , expected: null , msg: '+55 21 novo movel'},
{before: '+5521998766789' , expected: null , msg: '+55 21 novo movel'},
{before: '(21) 99876-6789' , expected: null , msg: '21 novo movel'},
{before: '21 99876-6789' , expected: null , msg: '21 novo movel'},
{before: '21998766789' , expected: null , msg: '21 novo movel'},
{before: '0 (21) 99876-6789' , expected: null , msg: '021 novo movel'},
{before: '(021) 99876-6789' , expected: null , msg: '021 novo movel'},
{before: '021 99876-6789' , expected: null , msg: '021 novo movel'},
{before: '021998766789' , expected: null , msg: '021 novo movel'},
{before: '+54 21 98766789' , expected: null , msg: 'internacional de outro pais ficticio'},
{before: '+55 (21) 98.76-67.89' , expected: '+55 (21) 998.76-67.89', msg: '+55 21 velho movel'},
{before: '+55 (21) 9876-6789' , expected: '+55 (21) 99876-6789' , msg: '+55 21 velho movel'},
{before: '+55 21 9876-6789' , expected: '+55 21 99876-6789' , msg: '+55 21 velho movel'},
{before: '+55 21 98766789' , expected: '+55 21 998766789' , msg: '+55 21 velho movel'},
{before: '+55 2198766789' , expected: '+55 21998766789' , msg: '+55 21 velho movel'},
{before: '+552198766789' , expected: '+5521998766789' , msg: '+55 21 velho movel'},
{before: '(21) 9876-6789' , expected: '(21) 99876-6789' , msg: '21 velho movel'},
{before: '21 9876-6789' , expected: '21 99876-6789' , msg: '21 velho movel'},
{before: '2198766789' , expected: '21998766789' , msg: '21 velho movel'},
{before: '0 (21) 9876-6789' , expected: '0 (21) 99876-6789' , msg: '021 velho movel'},
{before: '(021) 9876-6789' , expected: '(021) 99876-6789' , msg: '021 velho movel'},
{before: '021 9876-6789' , expected: '021 99876-6789' , msg: '021 velho movel'},
{before: '02198766789' , expected: '021998766789' , msg: '021 velho movel'},
{before: '099 (21) 9876-6789' , expected: '099 (21) 99876-6789' , msg: '021 velho movel com operadora ficticia 99'},
{before: '(09921) 9876-6789' , expected: '(09921) 99876-6789' , msg: '021 velho movel com operadora ficticia 99'},
{before: '09921 9876-6789' , expected: '09921 99876-6789' , msg: '021 velho movel com operadora ficticia 99'},
{before: '0992198766789' , expected: '09921998766789' , msg: '021 velho movel com operadora ficticia 99'},
{before: '9099 (21) 9876-6789' , expected: '9099 (21) 99876-6789' , msg: '021 velho movel a cobrar'},
{before: '909921 9876-6789' , expected: '909921 99876-6789' , msg: '021 velho movel a cobrar'},
{before: '90992198766789' , expected: '909921998766789' , msg: '021 velho movel a cobrar'},
{before: '', expected: null, msg: 'vazio'}
];
function runTestsAndLogResults() {
var num_successes = 0;
for (var i = 0; i < unitTests.length; i++) {
var u = unitTests[i];
var r = assertWorks(u.before, u.expected, u.msg, i);
if (r.success) {
num_successes++;
} else {
Logger.log(r.msg);
}
}
Logger.log(num_successes + '/' + unitTests.length + ' have succeeded');
if (num_successes == unitTests.length) {
Logger.log('This was a triumph! Huge success!');
} else {
Logger.log('You FAIL!');
}
}
@joelwallis

Cool! I didn't know this Google Apps' feature. Man, put this script in a Github repo, so you can accept pull requests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment