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.

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