import 'dart:async'; import 'package:flutter/foundation.dart'; class SpamProtectionService extends ChangeNotifier { // Vote limits per user static const int maxVotesPerHour = 20; static const int maxVotesPerMinute = 5; static const int maxSuggestionsPerHour = 10; static const int maxSuggestionsPerMinute = 3; // Cooldown periods (in seconds) static const int voteCooldown = 2; static const int suggestionCooldown = 10; // User activity tracking final Map> _userVotes = {}; final Map> _userSuggestions = {}; final Map _lastVoteTime = {}; final Map _lastSuggestionTime = {}; final Map _consecutiveActions = {}; // Blocked users (temporary) final Map _blockedUsers = {}; String getCurrentUserId() { // In a real app, this would come from authentication return 'current_user_${DateTime.now().millisecondsSinceEpoch ~/ 1000000}'; } // Check if user can vote bool canVote(String userId) { // Check if user is temporarily blocked if (_isUserBlocked(userId)) { return false; } // Check cooldown if (_isOnCooldown(userId, _lastVoteTime, voteCooldown)) { return false; } // Check rate limits if (!_checkRateLimit(userId, _userVotes, maxVotesPerMinute, maxVotesPerHour)) { return false; } return true; } // Check if user can suggest songs bool canSuggest(String userId) { // Check if user is temporarily blocked if (_isUserBlocked(userId)) { return false; } // Check cooldown if (_isOnCooldown(userId, _lastSuggestionTime, suggestionCooldown)) { return false; } // Check rate limits if (!_checkRateLimit(userId, _userSuggestions, maxSuggestionsPerMinute, maxSuggestionsPerHour)) { return false; } return true; } // Record a vote action void recordVote(String userId) { final now = DateTime.now(); // Add to vote history _userVotes.putIfAbsent(userId, () => []).add(now); _lastVoteTime[userId] = now; // Track consecutive actions for spam detection _incrementConsecutiveActions(userId); // Clean old entries _cleanOldEntries(_userVotes[userId]!); // Check for suspicious behavior _checkForSpam(userId); notifyListeners(); } // Record a suggestion action void recordSuggestion(String userId) { final now = DateTime.now(); // Add to suggestion history _userSuggestions.putIfAbsent(userId, () => []).add(now); _lastSuggestionTime[userId] = now; // Track consecutive actions for spam detection _incrementConsecutiveActions(userId); // Clean old entries _cleanOldEntries(_userSuggestions[userId]!); // Check for suspicious behavior _checkForSpam(userId); notifyListeners(); } // Get remaining cooldown time in seconds int getVoteCooldownRemaining(String userId) { final lastVote = _lastVoteTime[userId]; if (lastVote == null) return 0; final elapsed = DateTime.now().difference(lastVote).inSeconds; return (voteCooldown - elapsed).clamp(0, voteCooldown); } int getSuggestionCooldownRemaining(String userId) { final lastSuggestion = _lastSuggestionTime[userId]; if (lastSuggestion == null) return 0; final elapsed = DateTime.now().difference(lastSuggestion).inSeconds; return (suggestionCooldown - elapsed).clamp(0, suggestionCooldown); } // Get user activity stats Map getUserStats(String userId) { final now = DateTime.now(); final hourAgo = now.subtract(const Duration(hours: 1)); final minuteAgo = now.subtract(const Duration(minutes: 1)); final votesThisHour = _userVotes[userId]?.where((time) => time.isAfter(hourAgo)).length ?? 0; final votesThisMinute = _userVotes[userId]?.where((time) => time.isAfter(minuteAgo)).length ?? 0; final suggestionsThisHour = _userSuggestions[userId]?.where((time) => time.isAfter(hourAgo)).length ?? 0; final suggestionsThisMinute = _userSuggestions[userId]?.where((time) => time.isAfter(minuteAgo)).length ?? 0; return { 'votesThisHour': votesThisHour, 'votesThisMinute': votesThisMinute, 'suggestionsThisHour': suggestionsThisHour, 'suggestionsThisMinute': suggestionsThisMinute, 'maxVotesPerHour': maxVotesPerHour, 'maxVotesPerMinute': maxVotesPerMinute, 'maxSuggestionsPerHour': maxSuggestionsPerHour, 'maxSuggestionsPerMinute': maxSuggestionsPerMinute, }; } // Check if user is blocked bool _isUserBlocked(String userId) { final blockTime = _blockedUsers[userId]; if (blockTime == null) return false; // Unblock after 5 minutes if (DateTime.now().difference(blockTime).inMinutes >= 5) { _blockedUsers.remove(userId); return false; } return true; } // Check cooldown bool _isOnCooldown(String userId, Map lastActionTime, int cooldownSeconds) { final lastAction = lastActionTime[userId]; if (lastAction == null) return false; return DateTime.now().difference(lastAction).inSeconds < cooldownSeconds; } // Check rate limits bool _checkRateLimit(String userId, Map> userActions, int maxPerMinute, int maxPerHour) { final actions = userActions[userId] ?? []; final now = DateTime.now(); // Count actions in the last minute final actionsLastMinute = actions.where((time) => now.difference(time).inMinutes < 1).length; if (actionsLastMinute >= maxPerMinute) return false; // Count actions in the last hour final actionsLastHour = actions.where((time) => now.difference(time).inHours < 1).length; if (actionsLastHour >= maxPerHour) return false; return true; } // Clean old entries (older than 1 hour) void _cleanOldEntries(List entries) { final oneHourAgo = DateTime.now().subtract(const Duration(hours: 1)); entries.removeWhere((time) => time.isBefore(oneHourAgo)); } // Track consecutive actions for spam detection void _incrementConsecutiveActions(String userId) { _consecutiveActions[userId] = (_consecutiveActions[userId] ?? 0) + 1; // Reset after some time of inactivity Timer(const Duration(seconds: 30), () { _consecutiveActions[userId] = 0; }); } // Check for spam behavior and block if necessary void _checkForSpam(String userId) { final consecutive = _consecutiveActions[userId] ?? 0; // Block user if too many consecutive actions if (consecutive > 15) { _blockedUsers[userId] = DateTime.now(); if (kDebugMode) { print('User $userId temporarily blocked for spam behavior'); } } } // Get block status message String? getBlockMessage(String userId) { final blockTime = _blockedUsers[userId]; if (blockTime == null) return null; final remaining = 5 - DateTime.now().difference(blockTime).inMinutes; if (remaining <= 0) { _blockedUsers.remove(userId); return null; } return 'You are temporarily blocked for $remaining more minutes due to excessive activity.'; } // Clear all data (for testing) void clearAllData() { _userVotes.clear(); _userSuggestions.clear(); _lastVoteTime.clear(); _lastSuggestionTime.clear(); _consecutiveActions.clear(); _blockedUsers.clear(); notifyListeners(); } }