You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

262 lines
8.7 KiB

  1. import { Injectable, Logger, Scope } from '@nestjs/common';
  2. import puppeteer, { Browser, Page } from 'puppeteer';
  3. import pdfparse from 'pdf-parse';
  4. import { getUnixTime, parse } from 'date-fns';
  5. import { certidaoResult } from '../../utils/certidao.utils';
  6. import { Situacao } from 'src/enums/situacao.enum';
  7. import { CapMonster } from 'src/helpers/capMonster';
  8. import { TwoCaptcha } from 'src/helpers/twoCaptcha';
  9. import { CertidaoNegativa } from 'src/interfaces';
  10. @Injectable({ scope: Scope.REQUEST })
  11. export class ScraperCndMtServise {
  12. private _page: Page;
  13. private browser: Browser;
  14. private cndFile: Buffer;
  15. private resultadoScraping: CertidaoNegativa;
  16. private logger = new Logger('ScraperCndMtServise');
  17. constructor() { }
  18. async scraperCndMt(inscricao: string) {
  19. try {
  20. await this.paginaInicial(inscricao);
  21. if(this.cndFile) {
  22. await this.capturandoInformacoesPdf(this.cndFile);
  23. } else {
  24. const selecionandoTabela = await this._page.$$('table');
  25. const selecionandoBodyTabela = await selecionandoTabela[2].$$('tbody > tr');
  26. const selecionandoConteudoDiv = await selecionandoBodyTabela[2].$$('td > div');
  27. const mensagemCndPositiva = await selecionandoConteudoDiv[0].$eval(
  28. 'font' ,
  29. elem => String(elem.textContent).trim(),
  30. );
  31. if(mensagemCndPositiva.includes('não são suficientes')) {
  32. this.logger.log('Certidão Positiva');
  33. return certidaoResult(Situacao.Positiva)
  34. }
  35. }
  36. await this._page.waitFor(8000);
  37. return this.resultadoScraping;
  38. } catch (error) {
  39. this.logger.error(error.message);
  40. return certidaoResult(Situacao.FalhaConsulta);
  41. } finally {
  42. await this._page.close();
  43. await this.browser.close();
  44. }
  45. }
  46. private async paginaInicial(inscricao: string): Promise<any> {
  47. let tentativas = 0;
  48. do {
  49. await this.carregarNavegadorPaginaInicial();
  50. this.logger.log(`Gerando nova CND para a inscrição ${inscricao}...`);
  51. await this.preencheFormulario(inscricao);
  52. await this.resolveCaptcha();
  53. await this._page.waitForNavigation();
  54. const validandoResolucaoDoCaptcha = await this._page.$$eval('form > font > b', mensagem => mensagem.map(texto => texto.textContent))
  55. if(validandoResolucaoDoCaptcha[0]) {
  56. if(validandoResolucaoDoCaptcha[0].includes('Código de caracteres inválido')){
  57. this.logger.log(`Captcha incorreto!`);
  58. tentativas += 1;
  59. await this.browser.close();
  60. }
  61. }
  62. if(!validandoResolucaoDoCaptcha[0]){
  63. this.logger.log(`Captcha para a inscrição ${inscricao} foi resolvido`);
  64. await this.capturaArquivoNaRequisicao();
  65. break;
  66. }
  67. } while (tentativas != 2);
  68. }
  69. private async carregarNavegadorPaginaInicial() {
  70. this.browser = await puppeteer.launch({
  71. defaultViewport: null,
  72. });
  73. this._page = await this.browser.newPage();
  74. await this._page.goto(
  75. 'https://www.sefaz.mt.gov.br/cnd/certidao/servlet/ServletRotd?origem=60',
  76. {
  77. waitUntil: 'load',
  78. }
  79. )
  80. return this._page;
  81. }
  82. private async preencheFormulario(inscricao: string) {
  83. await this._page.waitForSelector('#ModeloCertidao');
  84. await this._page.click('#ModeloCertidao');
  85. await this._page.waitForSelector('#tipoDoct');
  86. const selecionandoOpcaoCnpj = await this._page.$$('#tipoDoct');
  87. await selecionandoOpcaoCnpj[1].click();
  88. await this._page.waitForSelector('#numrDoctCNPJ');
  89. await this._page.type("#numrDoctCNPJ", inscricao, { delay: 250 });
  90. }
  91. private async capturaArquivoNaRequisicao (): Promise<any> {
  92. const client = await this._page.target().createCDPSession();
  93. let pdfBase64: Array<string> = [];
  94. let responseObj: Object;
  95. client.send('Fetch.enable', {
  96. patterns: [
  97. {
  98. requestStage: 'Response',
  99. },
  100. ],
  101. })
  102. await client.on('Fetch.requestPaused', async (reqEvent) => {
  103. const { requestId } = reqEvent;
  104. this.logger.log('Requisição pausada')
  105. let responseHeaders = reqEvent.responseHeaders || [];
  106. let contentType = '';
  107. for (let elements of responseHeaders) {
  108. if (elements.name.toLowerCase() === 'content-type') {
  109. contentType = elements.value;
  110. }
  111. }
  112. if (contentType.endsWith('pdf')) {
  113. const foundHeaderIndex = responseHeaders.findIndex(
  114. (h) => h.name === "content-disposition"
  115. );
  116. const attachmentHeader = {
  117. name: "content-disposition",
  118. value: "attachment",
  119. };
  120. if (foundHeaderIndex) {
  121. responseHeaders[foundHeaderIndex] = attachmentHeader;
  122. } else {
  123. responseHeaders.push(attachmentHeader);
  124. }
  125. responseObj = await client.send('Fetch.getResponseBody', {
  126. requestId,
  127. });
  128. pdfBase64 = Object.values(responseObj);
  129. this.cndFile = new Buffer(pdfBase64[0], 'base64');
  130. if(this.cndFile){
  131. this.logger.log('Cnd capturada')
  132. }
  133. await client.send('Fetch.continueRequest', { requestId });
  134. } else {
  135. await client.send('Fetch.continueRequest', { requestId });
  136. }
  137. });
  138. await this._page.waitFor(25000)
  139. }
  140. private async capturandoInformacoesPdf(cndFile: Buffer): Promise<any> {
  141. let situacao = 0;
  142. this.logger.log('Extraindo informações do pdf')
  143. let data = await pdfparse(cndFile)
  144. if(data.text.includes('CERTIDÃO NEGATIVA')){
  145. situacao = 1;
  146. } else if(data.text.includes('CERTIDÃO POSITIVA COM EFEITOS DE NEGATIVA')) {
  147. situacao = 4;
  148. }
  149. let inicioSeletor = data.text.indexOf('válida até:');
  150. const posicaoInicialDataValidade = inicioSeletor + 12;
  151. const posicaoFinalDataValidade = posicaoInicialDataValidade + 10;
  152. let inicioSeletorDataEmissão = data.text.indexOf('Data da emissão:');
  153. const posicaoInicialDataEmissao = inicioSeletorDataEmissão + 17;
  154. const posicaoFinalDataEmissao = posicaoInicialDataEmissao + 10;
  155. const dataValidade = parse((data.text.slice(posicaoInicialDataValidade, posicaoFinalDataValidade)), 'dd/MM/yyyy', new Date());
  156. const dataEmissao = parse((data.text.slice(posicaoInicialDataEmissao, posicaoFinalDataEmissao)), 'dd/MM/yyyy', new Date());
  157. if(dataEmissao && dataValidade && cndFile) {
  158. this.logger.log('Scraping concluído');
  159. this.resultadoScraping = {
  160. situacao: situacao,
  161. dataEmissao: getUnixTime(dataEmissao),
  162. dataValidade: getUnixTime(dataValidade),
  163. file: cndFile
  164. }
  165. }
  166. }
  167. private async resolveCaptcha (): Promise<any> {
  168. const captchaBinary = await this._page.evaluate(() => {
  169. const img: HTMLImageElement = document.querySelector(
  170. '[src="/cnd/certidao/geradorcaracteres"]'
  171. )
  172. const canvas = document.createElement('canvas')
  173. canvas.width = img.width
  174. canvas.height = img.height
  175. const ctx = canvas.getContext('2d')
  176. ctx.drawImage(img, 0, 0)
  177. return canvas.toDataURL('image/png')
  178. })
  179. const captchaService = new CapMonster()
  180. const twoCaptchService = new TwoCaptcha()
  181. const matches = captchaBinary.match(/^data:(.+);base64,(.+)$/)
  182. if (matches.length !== 3) {
  183. throw new Error('Erro ao extrair imagem do Captcha')
  184. }
  185. let captcha;
  186. if (await captchaService.temSaldoDisponivel()) {
  187. this.logger.log(' Usando CapMonster...')
  188. captcha = await captchaService.resolver(matches[2]);
  189. } else if (await twoCaptchService.temSaldoDisponivel()) {
  190. this.logger.log(' Usando TwoCaptcha...')
  191. captcha = await twoCaptchService.resolveCaptcha(matches[2]);
  192. } else {
  193. this.logger.log('Não tem saldo para quebrar o captcha.');
  194. return {
  195. sucesso: false,
  196. mensagem: 'Não tem saldo para quebrar o captcha.'
  197. }
  198. }
  199. if (!captcha) throw new Error('Erro ao resolver o Captcha');
  200. await this._page.type('[name="caracteres"]', captcha, {delay: 250});
  201. const selecionandoElementoBotaoOk = await this._page.$$('#spanBotao');
  202. await selecionandoElementoBotaoOk[0].click();
  203. }
  204. }