INDEX / modules /SubscriptionManager.js
akra35567's picture
Update modules/SubscriptionManager.js
22dcb40 verified
/**
* ═══════════════════════════════════════════════════════════════════════════
* SUBSCRIPTION MANAGER - SISTEMA DE ASSINATURA ENTERPRISE
* ═══════════════════════════════════════════════════════════════════════════
* βœ… Controla acesso a features premium
* βœ… Rate limiting por tier (Free, Subscriber, Owner)
* βœ… Sistema de pontos/crΓ©ditos
* βœ… Logs de uso detalhados
* βœ… IntegraΓ§Γ£o com DONATE para upgrade
*
* πŸ“Š TIERS:
* - FREE (padrΓ£o): 1 uso/mΓͺs por feature, acesso bΓ‘sico
* - SUBSCRIBER: 1 uso/semana por feature, anΓ‘lise avanΓ§ada
* - OWNER: Ilimitado, modo ROOT
* ═══════════════════════════════════════════════════════════════════════════
*/
const fs = require('fs');
const path = require('path');
class SubscriptionManager {
constructor(config) {
this.config = config;
// ═══════════════════════════════════════════════════════════════════
// HF SPACES: Usar /tmp para garantir permissΓ΅es de escrita
// O HF Spaces tem sistema de arquivos somente-leitura em /
// ═══════════════════════════════════════════════════════════════════
// ForΓ§ar uso de /tmp no HF Spaces (sistema read-only)
this.dataPath = '/tmp/akira_data/subscriptions';
this.usagePath = path.join(this.dataPath, 'usage.json');
this.subscribersPath = path.join(this.dataPath, 'subscribers.json');
// Cria diretΓ³rio se nΓ£o existir - COM TRATAMENTO DE ERRO
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);
// Fallback para /tmp direto se falhar
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);
// Continuar sem diretΓ³rio - usar memΓ³ria apenas
this.dataPath = null;
}
}
// Carrega dados
this.subscribers = this.dataPath ? this._loadJSON(this.subscribersPath, {}) : {};
this.usage = this.dataPath ? this._loadJSON(this.usagePath, {}) : {};
// Limpa uso antigo periodicamente
if (this.dataPath) {
this._cleanOldUsage();
}
console.log('βœ… SubscriptionManager inicializado');
}
/**
* Verifica se usuΓ‘rio pode usar uma feature
* @returns { canUse: boolean, reason: string, remaining: number }
*/
canUseFeature(userId, featureName) {
try {
// Owner tem acesso ilimitado
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);
// Gera chave ΓΊnica
const key = `${userId}_${featureName}_${this._getWindowStart(window)}`;
// ObtΓ©m uso atual
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
};
}
// Atualiza uso
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 };
}
}
/**
* ObtΓ©m tier do usuΓ‘rio
*/
getUserTier(userId) {
if (this.config.isDono(userId)) return 'owner';
if (this.subscribers[userId]) return 'subscriber';
return 'free';
}
/**
* Subscreve um usuΓ‘rio
*/
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 };
}
}
/**
* Cancela assinatura
*/
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 };
}
}
/**
* Verifica se assinatura expirou
*/
isSubscriptionValid(userId) {
const sub = this.subscribers[userId];
if (!sub) return false;
const agora = new Date();
const expira = new Date(sub.expiraEm);
return agora < expira;
}
/**
* ObtΓ©m informaΓ§Γ΅es de assinatura
*/
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'
};
}
/**
* Formata mensagem de 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 '';
}
/**
* Gera relatΓ³rio de uso
*/
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))
};
}
/**
* ═════════════════════════════════════════════════════════════════════
* FUNÇÕES PRIVADAS
* ═════════════════════════════════════════════════════════════════════
*/
_getLimites(tier) {
const limites = {
free: {
usoPorPeriodo: 1,
features: ['whois', 'dns', 'nmap-basic']
},
subscriber: {
usoPorPeriodo: 4, // 1/semana
features: ['whois', 'dns', 'nmap', 'sqlmap', 'osint-basic', 'vulnerability-assessment']
},
owner: {
usoPorPeriodo: 999,
features: ['*'] // Tudo
}
};
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)) {
// MantΓ©m ΓΊltimos 90 dias
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);
// Se falhar, salvar em memΓ³ria apenas
return false;
}
}
}
module.exports = SubscriptionManager;