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 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
+ }
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 };