Skip to content

Instantly share code, notes, and snippets.

@phcostabh
Created November 11, 2013 17:41
Show Gist options
  • Select an option

  • Save phcostabh/7417157 to your computer and use it in GitHub Desktop.

Select an option

Save phcostabh/7417157 to your computer and use it in GitHub Desktop.
// Lib Fuse
var Fuse = require('./fuse.js'),
// Lib http do Node
http = require('http'),
// Seta a url do Solr
SOLR_HOST = process.argv[2].split(':')[0],
// Seta porta do SOLR
SOLR_PORT = parseInt(process.argv[2].split(':')[1] || 80, 10),
// Flag de Debug
DEBUG = parseInt(process.argv[3], 10) || false,
matches,
totalProcessed,
musicListLength,
streamContent = '';
// recuperar dados da stdin
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', function(chunk) {
streamContent += chunk;
});
process.stdin.on('end', function() {
queryData(streamContent);
streamContent = null;
});
function queryData(data) {
var singleQuery,
pathSearch,
partialPathSearch,
currentItem,
trash,
queryBoost,
queryOperator,
i,
searchTerm;
matches = [];
totalProcessed = 0;
if (DEBUG) {
console.error('DATA:', data);
console.error('SOLR_HOST:', SOLR_HOST);
console.error('SOLR_PORT:', SOLR_PORT);
console.time('profile');
}
try {
// Transforma em objeto JS
data = JSON.parse(data);
singleQuery = false;
} catch (ex) {
singleQuery = true;
data = [{
q: data
}];
}
// Lista de palavras a serem removidas do termo de busca
trash = 'versão|versao|version|participacao|participação|acústico|acustico|acoustic|' +
'instrumental|live|track|album|remix|radio|rádio|bonus|bônus|estúdio|estudio|studio|' +
'feat\\.?|part\\.?|ao\\s?vivo|speech|com|soundtrack|faixa|official|oficial|Unplugged|HD|Cover|lyrics?';
if (singleQuery) {
queryBoost = '{!boost%20b=log(h)}';
queryOperator = 'OR';
} else {
queryBoost = '';
queryOperator = 'AND';
}
// Estrutura inicial do path de busca
partialPathSearch = '/solr/letras/select/?wt=json&rows=3&omitHeader=true&q=' + queryBoost + '{!lucene%20q.op=' + queryOperator + '%20df=full_txt}t:2' + '%20AND%20full_txt:';
musicListLength = data.length;
for (i = musicListLength - 1; i >= 0; i--) {
// Pega o objeto do item atual
currentItem = data[i];
// Se não tiver query válida ou já possuir os itens cacheados, passa
// pro próximo
if (!currentItem.q || (currentItem.i && currentItem.d && currentItem.iar && currentItem.g)) {
matches.push(currentItem);
done();
continue;
}
searchTerm = currentItem.q
.replace(/(\n|\r\n|\r)/g, '')
// Remove possíveis formatos de arquivo de música
.replace(/\.(3gp|act|AIFF|aac|ALAC|amr|atrac|Au|awb|dct|dss|dvf|flac|gsm|iklax|IVS|m4a|m4p|mmf|mp3|mpc|msv|ogg|Opus|ra|rm|raw|TTA|vox|wav|wma)$/, '')
// Normaliza os espaços
.replace(/\s{2,}/, ' ')
// Trata strings começando com "track" ou "faixa", etc...
.replace(/^((track|faixa).?\d{1,2})?/i, '')
// Retira [...] e (...) que contenham palavras trash
.replace(new RegExp('(?:[\\[(]).*(?=' + trash + ').*(?:[\\])])', 'gi'), '')
// Normaliza os espaços novamente
.replace(/\s{2,}/, ' ')
// Escapa alguns caracteres p/ não bugar a busca no SOLR
.replace('\\', '\\\\')
.replace(/(\+|-|\||!|\(|\)|\{|\}|\[|\]|\^|~|\*|\?|:|"|;|\/)/g, "\\$&");
// Monta o path final do pacote HTTP
pathSearch = partialPathSearch + encodeURIComponent(searchTerm);
// Faz a busca no SOLR
solrQuery(pathSearch, currentItem, singleQuery);
}
}
/**
* Condição de saída do programa
* Checa se pode finalizar com sucesso ou erro
*
* @author Romano Augusto
* @since 02/05/2013
* @param string [error] Mensagem de erro da requisição
* http feita no Solr
*/
function done(error) {
if (error) {
if (DEBUG) {
console.error('Saindo com erro:', error);
console.timeEnd('profile');
}
process.exit(1);
}
if (++totalProcessed === musicListLength) {
console.log(JSON.stringify(matches));
if (DEBUG) {
console.error('FIM');
console.timeEnd('profile');
}
process.exit(0);
}
}
function txt_tirar_acentos(s) {
var re1 = /[ÁÀÂÃÄáàâãªä]/,
re2 = /[ÉÈÊËéèêë&]/,
re3 = /[óòôõöºÖÓÒÔÕ]/,
re4 = /[úùûüÚÙÛÜ]/,
re5 = /[íÍîïÏ]/;
s = s.replace(re1, 'a')
.replace(re2, 'e')
.replace(re3, 'o')
.replace(re4, 'u')
.replace(re5, 'i')
.replace('ç', 'c')
.replace('Ç', 'c');
return s;
}
/**
* Recebe resposta do Solr e parseia os resultados.
*
* @param chunk Array Dados do solr
* @param currentItem Object item atual de musicList
* @param [probablyList=false] Boolean Retornar prováveis resultados.
*/
function matchSolrResults(res, currentItem, probablyList) {
if (res.numFound > 0) {
var found = null,
query = currentItem.q,
f = new Fuse(res.docs, {
keys: ['full_txt'],
caseSensitive: false,
//threshold: 0.2
}),
search = f.search(query);
// Se o bitap retornar uma única ocorrência, é ela
if (search.length === 1) {
found = search[0];
} else {
var query_ascii = txt_tirar_acentos(query.toLowerCase());
// Se o bitap retornar mais de uma ocorrência,
// decidimos a melhor opção, caso haja
search.forEach(function(result) {
// Se não encontrar a ocorrência de artista e música,
// abandona o for
if (
// Tenta achar o artista
(query_ascii.lastIndexOf(
txt_tirar_acentos(result.art.toLowerCase())
) === -1) ||
// Tenta achar a música
(query_ascii.lastIndexOf(
txt_tirar_acentos(result.txt.toLowerCase())
) === -1)) {
return false;
}
var words = txt_tirar_acentos(result.full_txt.toLowerCase()).split(' '),
totWords = words.length,
appearings = 0,
i;
for (i = 0; i < totWords; i++) {
if (query_ascii.indexOf(words[i]) !== -1) {
appearings++;
}
// Se o resultado do bitap, aparece em 80% ou mais
// da query_ascii, assumimos a melhor opção
if ((appearings / totWords) >= 0.8) {
found = result;
return false;
}
}
});
}
// se não encontrou e deve-se retornar a lista das músicas prováveis
if (found === null) {
if (probablyList === true) {
matches = search.map(function(el) {
el.d = el.dns;
el.u = el.url;
return el;
});
done();
}
} else {
// Id da música
currentItem.i = found.imu;
// Dns do artista
currentItem.d = found.dns;
// Id do artista
currentItem.iar = found.iar;
// Gêneros da música
currentItem.g = found.g ? found.g.split(',') : ['unknown'];
// Hits da música
if (true === isNaN(currentItem.h)) {
currentItem.h = found.h;
}
//Url da música
currentItem.u = found.url;
}
}
return currentItem;
}
function solrQuery(pathSearch, currentItem, singleQuery) {
http.get({
host: SOLR_HOST,
port: SOLR_PORT,
path: pathSearch
}, function(resp) {
resp.on('data', function(chunk) {
if (resp.statusCode === 200) {
matches.push(matchSolrResults(JSON.parse(chunk).response, currentItem, singleQuery));
} else {
matches.push(currentItem);
}
done();
});
}).on('error', function(e) {
done(e.message);
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment