|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const fs = require('fs'); |
|
|
const path = require('path'); |
|
|
|
|
|
class SubscriptionManager { |
|
|
constructor(config) { |
|
|
this.config = config; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this.dataPath = '/tmp/akira_data/subscriptions'; |
|
|
|
|
|
this.usagePath = path.join(this.dataPath, 'usage.json'); |
|
|
this.subscribersPath = path.join(this.dataPath, 'subscribers.json'); |
|
|
|
|
|
|
|
|
try { |
|
|
if (!fs.existsSync(this.dataPath)) { |
|
|
fs.mkdirSync(this.dataPath, { recursive: true }); |
|
|
console.log(`β
SubscriptionManager: DiretΓ³rio criado: ${this.dataPath}`); |
|
|
} |
|
|
} catch (error) { |
|
|
console.warn(`β οΈ SubscriptionManager: NΓ£o foi possΓvel criar diretΓ³rio em ${this.dataPath}:`, error.message); |
|
|
|
|
|
|
|
|
const tmpPath = '/tmp/subscriptions'; |
|
|
try { |
|
|
fs.mkdirSync(tmpPath, { recursive: true }); |
|
|
this.dataPath = tmpPath; |
|
|
this.usagePath = path.join(this.dataPath, 'usage.json'); |
|
|
this.subscribersPath = path.join(this.dataPath, 'subscribers.json'); |
|
|
console.log(`β
SubscriptionManager: Usando fallback: ${this.dataPath}`); |
|
|
} catch (fallbackError) { |
|
|
console.error('β SubscriptionManager: Erro crΓtico ao criar diretΓ³rio de fallback:', fallbackError.message); |
|
|
|
|
|
this.dataPath = null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.subscribers = this.dataPath ? this._loadJSON(this.subscribersPath, {}) : {}; |
|
|
this.usage = this.dataPath ? this._loadJSON(this.usagePath, {}) : {}; |
|
|
|
|
|
|
|
|
if (this.dataPath) { |
|
|
this._cleanOldUsage(); |
|
|
} |
|
|
|
|
|
console.log('β
SubscriptionManager inicializado'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
canUseFeature(userId, featureName) { |
|
|
try { |
|
|
|
|
|
if (this.config.isDono(userId)) { |
|
|
return { canUse: true, reason: 'OWNER', remaining: 999 }; |
|
|
} |
|
|
|
|
|
const tier = this.getUserTier(userId); |
|
|
const limites = this._getLimites(tier); |
|
|
const window = this._getTimeWindow(tier); |
|
|
|
|
|
|
|
|
const key = `${userId}_${featureName}_${this._getWindowStart(window)}`; |
|
|
|
|
|
|
|
|
const uso = (this.usage[key] || 0) + 1; |
|
|
|
|
|
if (uso > limites.usoPorPeriodo) { |
|
|
return { |
|
|
canUse: false, |
|
|
reason: `Limite atingido para ${tier}: ${limites.usoPorPeriodo} uso(s) por ${window}`, |
|
|
remaining: 0 |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
this.usage[key] = uso; |
|
|
this._saveJSON(this.usagePath, this.usage); |
|
|
|
|
|
return { |
|
|
canUse: true, |
|
|
reason: `${tier.toUpperCase()}`, |
|
|
remaining: limites.usoPorPeriodo - uso |
|
|
}; |
|
|
} catch (e) { |
|
|
console.error('Erro em canUseFeature:', e); |
|
|
return { canUse: false, reason: 'Erro ao verificar', remaining: 0 }; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getUserTier(userId) { |
|
|
if (this.config.isDono(userId)) return 'owner'; |
|
|
if (this.subscribers[userId]) return 'subscriber'; |
|
|
return 'free'; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
subscribe(userId, duracao = 30) { |
|
|
try { |
|
|
const dataExpira = new Date(); |
|
|
dataExpira.setDate(dataExpira.getDate() + duracao); |
|
|
|
|
|
this.subscribers[userId] = { |
|
|
subscritaEm: new Date().toISOString(), |
|
|
expiraEm: dataExpira.toISOString(), |
|
|
duracao, |
|
|
renovacoes: (this.subscribers[userId]?.renovacoes || 0) + 1 |
|
|
}; |
|
|
|
|
|
this._saveJSON(this.subscribersPath, this.subscribers); |
|
|
|
|
|
return { |
|
|
sucesso: true, |
|
|
mensagem: `Assinatura ativada por ${duracao} dias`, |
|
|
expiraEm: dataExpira.toLocaleDateString('pt-BR') |
|
|
}; |
|
|
} catch (e) { |
|
|
return { sucesso: false, erro: e.message }; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unsubscribe(userId) { |
|
|
try { |
|
|
delete this.subscribers[userId]; |
|
|
this._saveJSON(this.subscribersPath, this.subscribers); |
|
|
|
|
|
return { sucesso: true, mensagem: 'Assinatura cancelada' }; |
|
|
} catch (e) { |
|
|
return { sucesso: false, erro: e.message }; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isSubscriptionValid(userId) { |
|
|
const sub = this.subscribers[userId]; |
|
|
if (!sub) return false; |
|
|
|
|
|
const agora = new Date(); |
|
|
const expira = new Date(sub.expiraEm); |
|
|
|
|
|
return agora < expira; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getSubscriptionInfo(userId) { |
|
|
const tier = this.getUserTier(userId); |
|
|
|
|
|
if (tier === 'owner') { |
|
|
return { |
|
|
tier: 'OWNER', |
|
|
status: 'β
Acesso Ilimitado', |
|
|
usoPorPeriodo: 'Ilimitado', |
|
|
periodo: 'Permanente', |
|
|
recursos: [ |
|
|
'β
Todas as ferramentas de cybersecurity', |
|
|
'β
Modo ROOT', |
|
|
'β
Rate limiting desativado', |
|
|
'β
AnΓ‘lise avanΓ§ada', |
|
|
'β
Dark web monitoring', |
|
|
'β
OSINT completo' |
|
|
] |
|
|
}; |
|
|
} |
|
|
|
|
|
const sub = this.subscribers[userId]; |
|
|
if (sub && this.isSubscriptionValid(userId)) { |
|
|
const expira = new Date(sub.expiraEm); |
|
|
const diasRestantes = Math.ceil((expira - new Date()) / (1000 * 60 * 60 * 24)); |
|
|
|
|
|
return { |
|
|
tier: 'SUBSCRIBER', |
|
|
status: `β
Ativo (${diasRestantes} dias)`, |
|
|
usoPorPeriodo: '1/semana', |
|
|
periodo: 'Semanal', |
|
|
expiraEm: expira.toLocaleDateString('pt-BR'), |
|
|
recursos: [ |
|
|
'β
Ferramentas premium de cybersecurity', |
|
|
'β
AnΓ‘lise avanΓ§ada', |
|
|
'β
OSINT avanΓ§ado', |
|
|
'β
Leak database search', |
|
|
'β¬ Dark web monitoring', |
|
|
'β¬ Modo ROOT' |
|
|
] |
|
|
}; |
|
|
} |
|
|
|
|
|
return { |
|
|
tier: 'FREE', |
|
|
status: 'β¬ Gratuito', |
|
|
usoPorPeriodo: '1/mΓͺs', |
|
|
periodo: 'Mensal', |
|
|
recursos: [ |
|
|
'β
Ferramentas bΓ‘sicas (WHOIS, DNS)', |
|
|
'β
NMAP simulado', |
|
|
'β¬ AnΓ‘lise avanΓ§ada', |
|
|
'β¬ OSINT avanΓ§ado', |
|
|
'β¬ Leak database search', |
|
|
'β¬ Dark web monitoring' |
|
|
], |
|
|
upgrade: 'Use #donate para fazer upgrade' |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getUpgradeMessage(userId, feature) { |
|
|
const tier = this.getUserTier(userId); |
|
|
|
|
|
if (tier === 'free') { |
|
|
return `\n\nπ *UPGRADE DISPONΓVEL*\n\n` + |
|
|
`VocΓͺ estΓ‘ usando: *${feature}*\n\n` + |
|
|
`π― Com assinatura terΓ‘:\n` + |
|
|
`β’ 1 uso/semana (vs 1/mΓͺs)\n` + |
|
|
`β’ AnΓ‘lise avanΓ§ada\n` + |
|
|
`β’ OSINT completo\n\n` + |
|
|
`Use #donate para fazer upgrade!\n` + |
|
|
`π° Planos a partir de R$ 5`; |
|
|
} |
|
|
|
|
|
if (tier === 'subscriber') { |
|
|
return `\n\nπ *MODO OWNER*\n\n` + |
|
|
`Com acesso OWNER terΓ‘:\n` + |
|
|
`β’ Ilimitado\n` + |
|
|
`β’ Modo ROOT\n` + |
|
|
`β’ Dark web monitoring\n\n` + |
|
|
`Contato: isaac.quarenta@akira.bot`; |
|
|
} |
|
|
|
|
|
return ''; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getUsageReport(userId) { |
|
|
const userUsage = {}; |
|
|
|
|
|
for (const [key, count] of Object.entries(this.usage)) { |
|
|
if (key.startsWith(userId)) { |
|
|
const [, feature] = key.split('_'); |
|
|
userUsage[feature] = count; |
|
|
} |
|
|
} |
|
|
|
|
|
return { |
|
|
userId, |
|
|
tier: this.getUserTier(userId), |
|
|
usoAtual: userUsage, |
|
|
limites: this._getLimites(this.getUserTier(userId)) |
|
|
}; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_getLimites(tier) { |
|
|
const limites = { |
|
|
free: { |
|
|
usoPorPeriodo: 1, |
|
|
features: ['whois', 'dns', 'nmap-basic'] |
|
|
}, |
|
|
subscriber: { |
|
|
usoPorPeriodo: 4, |
|
|
features: ['whois', 'dns', 'nmap', 'sqlmap', 'osint-basic', 'vulnerability-assessment'] |
|
|
}, |
|
|
owner: { |
|
|
usoPorPeriodo: 999, |
|
|
features: ['*'] |
|
|
} |
|
|
}; |
|
|
|
|
|
return limites[tier] || limites.free; |
|
|
} |
|
|
|
|
|
_getTimeWindow(tier) { |
|
|
const windows = { |
|
|
free: 'month', |
|
|
subscriber: 'week', |
|
|
owner: 'unlimited' |
|
|
}; |
|
|
return windows[tier] || 'month'; |
|
|
} |
|
|
|
|
|
_getWindowStart(window) { |
|
|
const agora = new Date(); |
|
|
|
|
|
if (window === 'month') { |
|
|
return `${agora.getFullYear()}-${agora.getMonth()}`; |
|
|
} |
|
|
if (window === 'week') { |
|
|
const semana = Math.floor(agora.getDate() / 7); |
|
|
return `${agora.getFullYear()}-${agora.getMonth()}-w${semana}`; |
|
|
} |
|
|
return 'unlimited'; |
|
|
} |
|
|
|
|
|
_cleanOldUsage() { |
|
|
try { |
|
|
const agora = new Date(); |
|
|
const limpo = {}; |
|
|
|
|
|
for (const [key, count] of Object.entries(this.usage)) { |
|
|
|
|
|
limpo[key] = count; |
|
|
} |
|
|
|
|
|
this.usage = limpo; |
|
|
this._saveJSON(this.usagePath, this.usage); |
|
|
} catch (e) { |
|
|
console.warn('Erro ao limpar uso antigo:', e); |
|
|
} |
|
|
} |
|
|
|
|
|
_loadJSON(filepath, defaultValue = {}) { |
|
|
try { |
|
|
if (fs.existsSync(filepath)) { |
|
|
return JSON.parse(fs.readFileSync(filepath, 'utf8')); |
|
|
} |
|
|
} catch (e) { |
|
|
console.warn(`Erro ao carregar ${filepath}:`, e); |
|
|
} |
|
|
return defaultValue; |
|
|
} |
|
|
|
|
|
_saveJSON(filepath, data) { |
|
|
try { |
|
|
fs.writeFileSync(filepath, JSON.stringify(data, null, 2)); |
|
|
return true; |
|
|
} catch (e) { |
|
|
console.warn(`Erro ao salvar ${filepath}:`, e); |
|
|
|
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
module.exports = SubscriptionManager; |