team-2/CHALLENGE_2/sleepysound/lib/services/spam_protection_service.dart
2025-08-02 07:51:04 +02:00

255 lines
7.9 KiB
Dart

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<String, List<DateTime>> _userVotes = {};
final Map<String, List<DateTime>> _userSuggestions = {};
final Map<String, DateTime> _lastVoteTime = {};
final Map<String, DateTime> _lastSuggestionTime = {};
final Map<String, int> _consecutiveActions = {};
// Blocked users (temporary)
final Map<String, DateTime> _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;
}
// Allow first vote without cooldown for smooth user experience
final votes = _userVotes[userId] ?? [];
if (votes.isEmpty) {
return true; // First vote is always allowed
}
// Check cooldown for subsequent votes
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;
}
// Allow first suggestion without cooldown for smooth user experience
final suggestions = _userSuggestions[userId] ?? [];
if (suggestions.isEmpty) {
return true; // First suggestion is always allowed
}
// Check cooldown for subsequent suggestions
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<String, int> 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<String, DateTime> 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<String, List<DateTime>> 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<DateTime> 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();
}
}