Browse Source

Scraper de CNDs do Mato Grosso criado

master
andre.borges 2 years ago
commit
0cd1ef1cd4
22 changed files with 823 additions and 0 deletions
  1. +5
    -0
      .env
  2. +24
    -0
      .eslintrc.js
  3. +35
    -0
      .gitignore
  4. +4
    -0
      .prettierrc
  5. +73
    -0
      README.md
  6. +4
    -0
      nest-cli.json
  7. +82
    -0
      package.json
  8. +12
    -0
      src/app.module.ts
  9. +6
    -0
      src/enums/situacao.enum.ts
  10. +114
    -0
      src/helpers/capMonster.ts
  11. +114
    -0
      src/helpers/twoCaptcha.ts
  12. +4
    -0
      src/interfaces/ICaptchaResolver.ts
  13. +6
    -0
      src/interfaces/certidao-negativa.interface.ts
  14. +2
    -0
      src/interfaces/index.ts
  15. +4
    -0
      src/interfaces/scraper-data.interface.ts
  16. +7
    -0
      src/main.ts
  17. +26
    -0
      src/modules/scraper-cnd-mt/scraper-cnd-mt.module.ts
  18. +15
    -0
      src/modules/scraper-cnd-mt/scraper-cnd-mt.processor.ts
  19. +265
    -0
      src/modules/scraper-cnd-mt/scraper-cnd-mt.service.ts
  20. +1
    -0
      src/utils/index.d.ts
  21. +4
    -0
      tsconfig.build.json
  22. +16
    -0
      tsconfig.json

+ 5
- 0
.env View File

@@ -0,0 +1,5 @@
APP_REDIS_HOST=redis-cnds
APP_REDIS_PORT=6380

CAP_MONSTER_KEY =89310c8388da93bf17133bf399f517de
TWO_CAPTCHA_KEY=0a26ea85b64ea9797cc296337001f6ab

+ 24
- 0
.eslintrc.js View File

@@ -0,0 +1,24 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

+ 35
- 0
.gitignore View File

@@ -0,0 +1,35 @@
# compiled output
/dist
/node_modules

# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# OS
.DS_Store

# Tests
/coverage
/.nyc_output

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

+ 4
- 0
.prettierrc View File

@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

+ 73
- 0
README.md View File

@@ -0,0 +1,73 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo_text.svg" width="320" alt="Nest Logo" /></a>
</p>

[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest

<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->

## Description

[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.

## Installation

```bash
$ npm install
```

## Running the app

```bash
# development
$ npm run start

# watch mode
$ npm run start:dev

# production mode
$ npm run start:prod
```

## Test

```bash
# unit tests
$ npm run test

# e2e tests
$ npm run test:e2e

# test coverage
$ npm run test:cov
```

## Support

Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).

## Stay in touch

- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)

## License

Nest is [MIT licensed](LICENSE).

+ 4
- 0
nest-cli.json View File

@@ -0,0 +1,4 @@
{
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}

+ 82
- 0
package.json View File

@@ -0,0 +1,82 @@
{
"name": "scraper-cnd-mt",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"docker:build": "",
"docker:tag": "",
"docker:push": "",
"docker:publish": "npm run docker:tag && npm run docker:push"
},
"dependencies": {
"@infosimples/node_two_captcha": "^1.0.2",
"@nestjs/bull": "^0.1.2",
"@nestjs/common": "^7.6.18",
"@nestjs/config": "^0.4.0",
"@nestjs/core": "^7.0.0",
"@nestjs/platform-express": "^7.0.0",
"bull": "^3.15.0",
"data-fns": "^0.1.8",
"date-fns": "^2.12.0",
"pdf-parse": "^1.1.1",
"puppeteer": "^5.0.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^6.5.4"
},
"devDependencies": {
"@nestjs/cli": "^7.0.0",
"@nestjs/schematics": "^7.0.0",
"@nestjs/testing": "^7.0.0",
"@types/bull": "^3.14.0",
"@types/express": "^4.17.3",
"@types/jest": "25.1.4",
"@types/node": "^13.9.1",
"@types/pdf-parse": "^1.1.1",
"@types/puppeteer": "^3.0.1",
"@types/supertest": "^2.0.8",
"@typescript-eslint/eslint-plugin": "^2.23.0",
"@typescript-eslint/parser": "^2.23.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.0",
"eslint-plugin-import": "^2.20.1",
"jest": "^25.1.0",
"prettier": "^1.19.1",
"supertest": "^4.0.2",
"ts-jest": "25.2.1",
"ts-loader": "^6.2.1",
"ts-node": "^8.6.2",
"tsconfig-paths": "^3.9.0",
"typescript": "^3.7.4"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

+ 12
- 0
src/app.module.ts View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

import { ScraperCndMtModule } from './modules/scraper-cnd-mt/scraper-cnd-mt.module';

@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
ScraperCndMtModule
],
})
export class AppModule {}

+ 6
- 0
src/enums/situacao.enum.ts View File

@@ -0,0 +1,6 @@
export enum Situacao {
Negativa = 1,
Positiva,
FalhaConsulta,
PositivaEfeitoNegativa,
}

+ 114
- 0
src/helpers/capMonster.ts View File

@@ -0,0 +1,114 @@
import axios from 'axios'

import { ICaptchaResolver } from '../interfaces/ICaptchaResolver';

export class CapMonster implements ICaptchaResolver {
private readonly CAPTCHA_KEY: string = process.env.CAP_MONSTER_KEY
private readonly CAPTCHA_BALANCE: string =
'https://api.capmonster.cloud/getBalance'
private readonly CAPTCHA_CREATE: string =
'https://api.capmonster.cloud/createTask'
private readonly CAPTCHA_RESULT: string =
'https://api.capmonster.cloud/getTaskResult'

public async temSaldoDisponivel (): Promise<boolean> {
try {
const balanceData = {
clientKey: this.CAPTCHA_KEY
}

const { data } = await axios.post(this.CAPTCHA_BALANCE, balanceData)

if (data.errorId > 0) {
console.log(`[consultar-saldo-captcha] - ${data.errorCode}`)
return false
}

return data.balance > 0
} catch (error) {
console.log(`[consultar-saldo-captcha] - ${error.message}`)

return false
}
}

public async resolver (imageData: string): Promise<string> {
const taskId = await this.criar(imageData)

if (taskId === 0) return null

return await this.resultado(taskId)
}

private async criar (imageData: string): Promise<number> {
try {
const createTask = {
clientKey: this.CAPTCHA_KEY,
task: {
type: 'ImageToTextTask',
body: imageData
}
}

const { data } = await axios.post(this.CAPTCHA_CREATE, createTask)

if (data.errorId > 0) {
console.log(`[criar-captcha] - ${data.errorCode}`)
return 0
}

return data.taskId
} catch (error) {
console.log(`[criar-captcha] - ${error.message}`)

return 0
}
}

private async resultado (taskId: number): Promise<string> {
let tentativas = 0
let error: boolean = false
let captchaData = null

const getResultTask = {
clientKey: this.CAPTCHA_KEY,
taskId: taskId
}

do {
try {
const {
data: { errorId, errorCode, solution, status }
} = await axios.post(this.CAPTCHA_RESULT, getResultTask)

if (errorId > 0) {
console.log(
`[resultado-captcha] - Task: ${taskId} Erro: ${errorCode} Tentativas: ${tentativas}`
)
error = true
break
}

if (status === 'ready') {
captchaData = solution.text
break
}

tentativas += 1
await new Promise(r => setTimeout(r, 10 * 1000))
} catch (error) {
console.log(
`[resultado-captcha] - Task: ${taskId} Erro: ${error.message} Tentativas: ${tentativas}`
)
error = true
break
}
} while (tentativas < 20)

if (error) return null

return captchaData
}
}

export default new CapMonster()

+ 114
- 0
src/helpers/twoCaptcha.ts View File

@@ -0,0 +1,114 @@
import axios from 'axios'

export class TwoCaptcha {

private readonly CAPTCHA_KEY: string = 'TWO_CAPTCHA_KEY'
private readonly CAPTCHA_CREATE: string = 'http://2captcha.com/in.php'
private readonly CAPTCHA_RESULT: string = 'http://2captcha.com/res.php'
private readonly CAPTCHA_BALANCE: string = 'https://2captcha.com/res.php'

async resolveCaptcha (imageData: string) {
const captchaId = await this._sendCaptcha(imageData)
const captchaResolved = await this._getResponseCaptcha(captchaId)
return captchaResolved
}

public async temSaldoDisponivel (): Promise<boolean> {
try {
const balanceData = {
key: this.CAPTCHA_KEY,
action: 'getbalance',
json: 1
}
const { data: { request } } = await axios.get(this.CAPTCHA_BALANCE, { params: balanceData });

if ((request || '').includes('ERROR_')) {
console.log(`[consultar-saldo-captcha] - ${request}`);
return false;
}

return Number(request) > 0;
} catch (error) {
console.log(`[consultar-saldo-captcha] - ${error.message}`);

return false;
}
}

private async _sendCaptcha (imageData: string): Promise<string> {
try {
const captchaResponse = await axios.post(
this.CAPTCHA_CREATE,
{
method: 'base64',
key: this.CAPTCHA_KEY,
body: imageData,
json: 1
},
{ headers: { 'Content-Type': 'multipart/form-data' } }
)

let response: string = ''

if (captchaResponse.data && typeof captchaResponse.data === 'object') {
if (captchaResponse.data.status === 0)
throw new Error(captchaResponse.data.request)
response = captchaResponse.data.request
} else if (
captchaResponse.data &&
typeof captchaResponse.data === 'string'
) {
if (captchaResponse.data.indexOf('OK') === -1)
throw new Error(captchaResponse.data)
response = captchaResponse.data.split('|')[1]
}

return response
} catch (error) {
return new Promise((resolve, reject) => reject(error))
}
}

private async _getResponseCaptcha (captchaId: string): Promise<string> {
return new Promise((resolve, reject) => {
const interval = setInterval(async () => {
try {
const serverResponse = await axios.get(this.CAPTCHA_RESULT, {
params: {
key: this.CAPTCHA_KEY,
action: 'get',
id: captchaId,
json: 1
}
})

if (serverResponse.data.request !== 'CAPCHA_NOT_READY') {
clearInterval(interval)

let response = ''

if (
serverResponse.data &&
typeof serverResponse.data === 'object'
) {
if (serverResponse.data.status === 0)
throw new Error(serverResponse.data.request)
response = serverResponse.data.request
} else if (
serverResponse.data &&
typeof serverResponse.data === 'string'
) {
if (serverResponse.data.indexOf('OK') === -1)
throw new Error(serverResponse.data)
response = serverResponse.data.split('|')[1]
}

resolve(response)
}
} catch (e) {
reject(e)
}
}, 10 * 1000)
})
}
}

+ 4
- 0
src/interfaces/ICaptchaResolver.ts View File

@@ -0,0 +1,4 @@
export interface ICaptchaResolver {
temSaldoDisponivel(): Promise<boolean>;
resolver(imageData: string): Promise<string>;
}

+ 6
- 0
src/interfaces/certidao-negativa.interface.ts View File

@@ -0,0 +1,6 @@
export interface CertidaoNegativa {
situacao: number;
dataEmissao: number;
dataValidade?: number;
file?: Uint8Array;
}

+ 2
- 0
src/interfaces/index.ts View File

@@ -0,0 +1,2 @@
export * from './certidao-negativa.interface';
export * from './scraper-data.interface';

+ 4
- 0
src/interfaces/scraper-data.interface.ts View File

@@ -0,0 +1,4 @@
export interface ScrapeData {
inscricao: string;
antecipar?: boolean;
}

+ 7
- 0
src/main.ts View File

@@ -0,0 +1,7 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
await NestFactory.createApplicationContext(AppModule);
}
bootstrap();

+ 26
- 0
src/modules/scraper-cnd-mt/scraper-cnd-mt.module.ts View File

@@ -0,0 +1,26 @@
import { BullModule } from '@nestjs/bull';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';

import { ScraperMtProcessor } from './scraper-cnd-mt.processor';
import { ScraperCndMtServise } from './scraper-cnd-mt.service';

@Module({
imports: [
BullModule.registerQueueAsync({
name: 'mato-grosso',
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
redis: {
host: configService.get('APP_REDIS_HOST'),
port: +configService.get('APP_REDIS_PORT'),
}
}),
inject: [ConfigService],
})],
providers: [
ScraperCndMtServise,
ScraperMtProcessor
],
})
export class ScraperCndMtModule {}

+ 15
- 0
src/modules/scraper-cnd-mt/scraper-cnd-mt.processor.ts View File

@@ -0,0 +1,15 @@
import { Process, Processor } from "@nestjs/bull";
import { Job } from 'bull';

import { ScraperCndMtServise } from "./scraper-cnd-mt.service";

@Processor('mato-grosso')
export class ScraperMtProcessor {
constructor(private readonly scraperCndMtService: ScraperCndMtServise) {}

@Process({ name: 'mato-grosso', concurrency: 1})
handler(job: Job) {
const { inscricao } = job.data;
return this.scraperCndMtService.scraperCndMt(inscricao);
}
}

+ 265
- 0
src/modules/scraper-cnd-mt/scraper-cnd-mt.service.ts View File

@@ -0,0 +1,265 @@
import { Injectable, Logger, Scope } from '@nestjs/common';
import puppeteer, { Browser, Page } from 'puppeteer';
import pdfparse from 'pdf-parse';
import { getUnixTime } from 'date-fns';

import { certidaoResult } from '../../utils/certidao.utils';
import { Situacao } from 'src/enums/situacao.enum';
import { CapMonster } from 'src/helpers/capMonster';
import { TwoCaptcha } from 'src/helpers/twoCaptcha';
import { CertidaoNegativa } from 'src/interfaces';

@Injectable({ scope: Scope.REQUEST })
export class ScraperCndMtServise {
private _page: Page;
private browser: Browser;
private cndFile: Buffer;
private resultadoScraping: CertidaoNegativa;
private logger = new Logger('ScraperCndMtServise');

constructor() { }

async scraperCndMt(inscricao: string) {
try {
await this.paginaInicial(inscricao);
if(this.cndFile) {
await this.capturandoInformacoesPdf(this.cndFile);
} else {
const selecionandoTabela = await this._page.$$('table');
const selecionandoBodyTabela = await selecionandoTabela[2].$$('tbody > tr');
const selecionandoConteudoDiv = await selecionandoBodyTabela[2].$$('td > div');

const mensagemCndPositiva = await selecionandoConteudoDiv[0].$eval(
'font' ,
elem => String(elem.textContent).trim(),
);
if(mensagemCndPositiva.includes('não são suficientes')) {
this.logger.log('Certidão Positiva');
return certidaoResult(Situacao.Positiva)
}
}
await this._page.waitFor(8000)
console.log(this.resultadoScraping)
return this.resultadoScraping;
} catch (error) {
this.logger.error(error.message);
return certidaoResult(Situacao.FalhaConsulta);
} finally {
await this._page.close();
await this.browser.close();
}
}

private async paginaInicial(inscricao: string): Promise<any> {
let tentativas = 0;

do {

await this.carregarNavegadorPaginaInicial();

this.logger.log(`Gerando nova CND para a inscrição ${inscricao}...`);

await this.preencheFormulario(inscricao);

await this.resolveCaptcha();

await this._page.waitForNavigation();

const validandoResolucaoDoCaptcha = await this._page.$$eval('form > font > b', mensagem => mensagem.map(texto => texto.textContent))
if(validandoResolucaoDoCaptcha[0]) {
if(validandoResolucaoDoCaptcha[0].includes('Código de caracteres inválido')){
this.logger.log(`Captcha incorreto!`);
tentativas += 1;
await this.browser.close();
}
}

if(!validandoResolucaoDoCaptcha[0]){
this.logger.log(`Captcha para a inscrição ${inscricao} foi resolvido`);
await this.capturaArquivoNaRequisicao();

break;
}
} while (tentativas != 2);
}

private async carregarNavegadorPaginaInicial() {
this.browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
});

this._page = await this.browser.newPage();
await this._page.goto(
'https://www.sefaz.mt.gov.br/cnd/certidao/servlet/ServletRotd?origem=60',
{
waitUntil: 'load',
}
)

return this._page;
}

private async preencheFormulario(inscricao: string) {
await this._page.waitForSelector('#ModeloCertidao');
await this._page.click('#ModeloCertidao');

await this._page.waitForSelector('#tipoDoct');
const selecionandoOpcaoCnpj = await this._page.$$('#tipoDoct');
await selecionandoOpcaoCnpj[1].click();

await this._page.waitForSelector('#numrDoctCNPJ');
await this._page.type("#numrDoctCNPJ", inscricao, { delay: 250 });
}

private async capturaArquivoNaRequisicao (): Promise<any> {
const client = await this._page.target().createCDPSession();
let pdfBase64: Array<string> = [];
let responseObj: Object;

client.send('Fetch.enable', {
patterns: [
{
requestStage: 'Response',
},
],
})

await client.on('Fetch.requestPaused', async (reqEvent) => {
const { requestId } = reqEvent;
this.logger.log('Requisição pausada')

let responseHeaders = reqEvent.responseHeaders || [];
let contentType = '';

for (let elements of responseHeaders) {
if (elements.name.toLowerCase() === 'content-type') {
contentType = elements.value;
}
}

if (contentType.endsWith('pdf')) {
const foundHeaderIndex = responseHeaders.findIndex(
(h) => h.name === "content-disposition"
);
const attachmentHeader = {
name: "content-disposition",
value: "attachment",
};
if (foundHeaderIndex) {
responseHeaders[foundHeaderIndex] = attachmentHeader;
} else {
responseHeaders.push(attachmentHeader);
}

responseObj = await client.send('Fetch.getResponseBody', {
requestId,
});
pdfBase64 = Object.values(responseObj);
this.cndFile = new Buffer(pdfBase64[0], 'base64');

if(this.cndFile){
this.logger.log('Cnd capturada')
}
await client.send('Fetch.continueRequest', { requestId });
} else {
await client.send('Fetch.continueRequest', { requestId });
}
});
await this._page.waitFor(25000)
}

private async capturandoInformacoesPdf(cndFile: Buffer): Promise<any> {
let situacao = 0;
this.logger.log('Extraindo informações do pdf')

let data = await pdfparse(cndFile)
if(data.text.includes('CERTIDÃO NEGATIVA')){
situacao = 1;
} else if(data.text.includes('CERTIDÃO POSITIVA COM EFEITOS DE NEGATIVA')) {
situacao = 4;
}
let inicioSeletor = data.text.indexOf('válida até:');
const posicaoInicialDataValidade = inicioSeletor + 13;
const posicaoFinalDataValidade = posicaoInicialDataValidade + 10;
const dataValidade = new Date(data.text.slice(posicaoInicialDataValidade, posicaoFinalDataValidade));

let inicioSeletorDataEmissão = data.text.indexOf('Data da emissão:');
const posicaoInicialDataEmissao = inicioSeletorDataEmissão + 18;
const posicaoFinalDataEmissao = posicaoInicialDataEmissao + 10;

const dataEmissao = new Date(data.text.slice(posicaoInicialDataEmissao, posicaoFinalDataEmissao));
if(dataEmissao && dataValidade && cndFile) {
this.logger.log('Scraping concluído');
this.resultadoScraping = {
situacao: situacao,
dataEmissao: getUnixTime(dataEmissao),
dataValidade: getUnixTime(dataValidade),
file: cndFile
}
}
}

private async resolveCaptcha (): Promise<any> {
const captchaBinary = await this._page.evaluate(() => {
const img: HTMLImageElement = document.querySelector(
'[src="/cnd/certidao/geradorcaracteres"]'
)
const canvas = document.createElement('canvas')
canvas.width = img.width
canvas.height = img.height
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0)
return canvas.toDataURL('image/png')
})
const captchaService = new CapMonster()
const twoCaptchService = new TwoCaptcha()
const matches = captchaBinary.match(/^data:(.+);base64,(.+)$/)
if (matches.length !== 3) {
throw new Error('Erro ao extrair imagem do Captcha')
}
let captcha;
if (await captchaService.temSaldoDisponivel()) {
this.logger.log(' Usando CapMonster...')
captcha = await captchaService.resolver(matches[2]);
} else if (await twoCaptchService.temSaldoDisponivel()) {
this.logger.log(' Usando TwoCaptcha...')
captcha = await twoCaptchService.resolveCaptcha(matches[2]);
} else {
this.logger.log('Não tem saldo para quebrar o captcha.');
return {
sucesso: false,
mensagem: 'Não tem saldo para quebrar o captcha.'
}
}
if (!captcha) throw new Error('Erro ao resolver o Captcha');
await this._page.type('[name="caracteres"]', captcha, {delay: 250});

const selecionandoElementoBotaoOk = await this._page.$$('#spanBotao');
await selecionandoElementoBotaoOk[0].click();
}
}

+ 1
- 0
src/utils/index.d.ts View File

@@ -0,0 +1 @@
declare module '@infosimples/node_two_captcha';

+ 4
- 0
tsconfig.build.json View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

+ 16
- 0
tsconfig.json View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"esModuleInterop": true
},
"exclude": ["node_modules", "dist"]
}