br-agefy 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +65 -0
- package/package.json +19 -0
- package/por.traineddata +0 -0
- package/processador.js +118 -0
package/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const fsp = fs.promises;
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { v4: uuidv4 } = require('uuid');
|
|
6
|
+
const cors = require('cors');
|
|
7
|
+
const { processarDocumento } = require('./processador.js');
|
|
8
|
+
const PORT = process.env.PORT || 3000;
|
|
9
|
+
const app = express();
|
|
10
|
+
app.use(express.json({ limit: '15mb' }));
|
|
11
|
+
app.use(cors());
|
|
12
|
+
|
|
13
|
+
const uploadsPath = path.join(__dirname, 'uploads');
|
|
14
|
+
if (!fs.existsSync(uploadsPath)) {
|
|
15
|
+
fs.mkdirSync(uploadsPath);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
app.post('/verify', async (req, res) => {
|
|
19
|
+
let caminhoArquivo = "";
|
|
20
|
+
try {
|
|
21
|
+
const { imagem } = req.body;
|
|
22
|
+
if (!imagem) return res.status(400).json({ sucesso: false, mensagem: "Sem imagem" });
|
|
23
|
+
|
|
24
|
+
const base64Limpo = imagem.includes(',') ? imagem.split(',')[1] : imagem;
|
|
25
|
+
const buffer = Buffer.from(base64Limpo, 'base64');
|
|
26
|
+
|
|
27
|
+
// Salvamos como .jpg para fugir do erro de libspng do PNG
|
|
28
|
+
const nomeArquivo = `${uuidv4()}.jpg`;
|
|
29
|
+
caminhoArquivo = path.join(uploadsPath, nomeArquivo);
|
|
30
|
+
|
|
31
|
+
await fsp.writeFile(caminhoArquivo, buffer);
|
|
32
|
+
|
|
33
|
+
// Delay de segurança: 100ms para o Windows soltar o arquivo
|
|
34
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
35
|
+
|
|
36
|
+
console.log(`✅ Arquivo pronto para processar: ${nomeArquivo}`);
|
|
37
|
+
|
|
38
|
+
const resultado = await processarDocumento(caminhoArquivo);
|
|
39
|
+
|
|
40
|
+
// Deleta o original
|
|
41
|
+
if (fs.existsSync(caminhoArquivo)) fs.unlinkSync(caminhoArquivo);
|
|
42
|
+
|
|
43
|
+
if (!resultado || !resultado.sucesso) {
|
|
44
|
+
return res.status(403).json({
|
|
45
|
+
sucesso: false,
|
|
46
|
+
codigo: "INVALID_DOCUMENT",
|
|
47
|
+
mensagem: resultado ? resultado.mensagem : "Erro no processamento"
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return res.json({
|
|
52
|
+
sucesso: true,
|
|
53
|
+
codigo: "VERIFIED",
|
|
54
|
+
idade: resultado.idade,
|
|
55
|
+
cpfValido: resultado.cpfValido
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error("❌ Erro na Rota:", err.message);
|
|
60
|
+
if (caminhoArquivo && fs.existsSync(caminhoArquivo)) try { fs.unlinkSync(caminhoArquivo); } catch(e){}
|
|
61
|
+
return res.status(500).json({ sucesso: false, codigo: "SERVER_ERROR", mensagem: err.message });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
app.listen(PORT, () => console.log("🔥 Agefy API rodando em http://localhost:3000"));
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "br-agefy",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Um sistema criado para desenvolvedores Brasileiros verificarem a idade de seus usuários sem ter que gastar nada.",
|
|
5
|
+
"main": "processador.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "ISC",
|
|
12
|
+
"type": "commonjs",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"cpf-cnpj-validator": "^1.0.3",
|
|
15
|
+
"sharp": "^0.34.5",
|
|
16
|
+
"tesseract.js": "^7.0.0",
|
|
17
|
+
"uuid": "^13.0.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/por.traineddata
ADDED
|
Binary file
|
package/processador.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
const sharp = require('sharp');
|
|
2
|
+
const Tesseract = require('tesseract.js');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { cpf: cpfValidator } = require('cpf-cnpj-validator');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Agefy: Motor de OCR e Validação de Identidade
|
|
9
|
+
* Desenvolvido por Davi de Castro
|
|
10
|
+
*/
|
|
11
|
+
async function processarDocumento(caminho) {
|
|
12
|
+
// Define o nome do arquivo temporário para o processamento
|
|
13
|
+
const caminhoTratado = `${caminho}_treated.jpg`;
|
|
14
|
+
const diretorio = path.dirname(caminho);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// 1. Garantia de Infraestrutura: Cria a pasta se ela não existir
|
|
18
|
+
if (!fs.existsSync(diretorio)) {
|
|
19
|
+
fs.mkdirSync(diretorio, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 2. Verificação de Entrada
|
|
23
|
+
if (!fs.existsSync(caminho)) {
|
|
24
|
+
throw new Error("Arquivo original não encontrado no caminho especificado.");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 3. Pré-processamento com Sharp (Otimizado para Tesseract)
|
|
28
|
+
// Aplicamos grayscale e threshold para remover ruídos do fundo do RG/CIN
|
|
29
|
+
await sharp(caminho)
|
|
30
|
+
.resize(1300)
|
|
31
|
+
.grayscale()
|
|
32
|
+
.threshold(120)
|
|
33
|
+
.jpeg({ quality: 90 })
|
|
34
|
+
.toFile(caminhoTratado);
|
|
35
|
+
|
|
36
|
+
// 4. Execução do OCR
|
|
37
|
+
const { data: { text } } = await Tesseract.recognize(caminhoTratado, 'por');
|
|
38
|
+
|
|
39
|
+
if (!text || text.trim().length < 10) {
|
|
40
|
+
return { sucesso: false, mensagem: "Não foi possível extrair texto legível da imagem." };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 5. Validação de Autenticidade Básica
|
|
44
|
+
const textoNormalizado = text.normalize("NFD")
|
|
45
|
+
.replace(/[\u0300-\u036f]/g, "")
|
|
46
|
+
.toUpperCase()
|
|
47
|
+
.replace(/\s+/g, "");
|
|
48
|
+
|
|
49
|
+
if (!textoNormalizado.includes("REPUBLICAFEDERATIVADOBRASIL")) {
|
|
50
|
+
return { sucesso: false, mensagem: "O documento não parece ser uma Identidade Brasileira oficial." };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 6. Extração de Dados com Regex
|
|
54
|
+
const regexCPF = /\d{3}[.\s]?\d{3}[.\s]?\d{3}[-\s]?\d{2}/;
|
|
55
|
+
const regexData = /\d{2}\/\d{2}\/\d{4}/g;
|
|
56
|
+
|
|
57
|
+
const cpfEncontrado = text.match(regexCPF);
|
|
58
|
+
const datasEncontradas = text.match(regexData);
|
|
59
|
+
|
|
60
|
+
if (!cpfEncontrado || !datasEncontradas || datasEncontradas.length < 2) {
|
|
61
|
+
return { sucesso: false, mensagem: "CPF ou datas (nascimento/validade) não detectados." };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 7. Validação de CPF
|
|
65
|
+
const cpfLimpo = cpfEncontrado[0].replace(/[^\d]/g, '');
|
|
66
|
+
if (!cpfValidator.isValid(cpfLimpo)) {
|
|
67
|
+
return { sucesso: false, mensagem: "O CPF lido é matematicamente inválido." };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 8. Cálculo de Idade e Validade
|
|
71
|
+
// Assumindo: data[0] = Nascimento, data[1] = Validade (Padrão CIN)
|
|
72
|
+
const [diaN, mesN, anoN] = datasEncontradas[0].split('/').map(Number);
|
|
73
|
+
const [diaV, mesV, anoV] = datasEncontradas[1].split('/').map(Number);
|
|
74
|
+
|
|
75
|
+
const dataAtual = new Date();
|
|
76
|
+
const anoAtual = dataAtual.getFullYear();
|
|
77
|
+
|
|
78
|
+
if (anoV < anoAtual) {
|
|
79
|
+
return { sucesso: false, mensagem: "Documento expirado." };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const dataNascimento = new Date(anoN, mesN - 1, diaN);
|
|
83
|
+
let idade = anoAtual - dataNascimento.getFullYear();
|
|
84
|
+
|
|
85
|
+
if (dataAtual.getMonth() < dataNascimento.getMonth() ||
|
|
86
|
+
(dataAtual.getMonth() === dataNascimento.getMonth() && dataAtual.getDate() < dataNascimento.getDate())) {
|
|
87
|
+
idade--;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 9. Retorno de Sucesso
|
|
91
|
+
return {
|
|
92
|
+
sucesso: true,
|
|
93
|
+
dados: {
|
|
94
|
+
idade: idade,
|
|
95
|
+
cpf: cpfLimpo,
|
|
96
|
+
validade: datasEncontradas[1]
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error(`[Agefy Engine Error]: ${error.message}`);
|
|
102
|
+
return {
|
|
103
|
+
sucesso: false,
|
|
104
|
+
mensagem: "Erro interno ao processar o documento."
|
|
105
|
+
};
|
|
106
|
+
} finally {
|
|
107
|
+
// 10. Limpeza de Arquivos Temporários (Privacidade/LGPD)
|
|
108
|
+
if (fs.existsSync(caminhoTratado)) {
|
|
109
|
+
try {
|
|
110
|
+
fs.unlinkSync(caminhoTratado);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error("[Agefy Security]: Erro ao deletar rastro temporário.");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = { processarDocumento };
|