@ -31,20 +31,8 @@ class CustomFilter extends Model
'account',
];
const MAX_LIMIT = 20;
const MAX_KEYWORDS_PER_FILTER = 10;
const MAX_STATUSES_PER_FILTER = 10;
const MAX_CONTENT_SCAN_LEN = 1000;
const MAX_KEYWORD_LEN = 40;
const MAX_PER_HOUR = 40;
const MAX_UPDATES_PER_HOUR = 40;
const EXPIRATION_DURATIONS = [
1800, // 30 minutes
3600, // 1 hour
@ -60,6 +48,20 @@ class CustomFilter extends Model
const ACTION_BLUR = 2;
protected static ?int $maxContentScanLimit = null;
protected static ?int $maxFiltersPerUser = null;
protected static ?int $maxKeywordsPerFilter = null;
protected static ?int $maxKeywordsLength = null;
protected static ?int $maxPatternLength = null;
protected static ?int $maxCreatePerHour = null;
protected static ?int $maxUpdatesPerHour = null;
public function account()
{
return $this->belongsTo(Profile::class, 'profile_id');
@ -166,6 +168,11 @@ class CustomFilter extends Model
$model->shouldInvalidateCache = true;
});
static::updating(function ($model) {
$model->prepareContextForStorage();
$model->shouldInvalidateCache = true;
});
static::deleting(function ($model) {
$model->shouldInvalidateCache = true;
});
@ -197,6 +204,69 @@ class CustomFilter extends Model
Cache::forget("filters:v3:{$this->profile_id}");
}
public static function getMaxContentScanLimit(): int
{
if (self::$maxContentScanLimit === null) {
self::$maxContentScanLimit = config('instance.custom_filters.max_content_scan_limit', 2500);
}
return self::$maxContentScanLimit;
}
public static function getMaxFiltersPerUser(): int
{
if (self::$maxFiltersPerUser === null) {
self::$maxFiltersPerUser = config('instance.custom_filters.max_filters_per_user', 20);
}
return self::$maxFiltersPerUser;
}
public static function getMaxKeywordsPerFilter(): int
{
if (self::$maxKeywordsPerFilter === null) {
self::$maxKeywordsPerFilter = config('instance.custom_filters.max_keywords_per_filter', 10);
}
return self::$maxKeywordsPerFilter;
}
public static function getMaxKeywordLength(): int
{
if (self::$maxKeywordsLength === null) {
self::$maxKeywordsLength = config('instance.custom_filters.max_keyword_length', 40);
}
return self::$maxKeywordsLength;
}
public static function getMaxPatternLength(): int
{
if (self::$maxPatternLength === null) {
self::$maxPatternLength = config('instance.custom_filters.max_pattern_length', 10000);
}
return self::$maxPatternLength;
}
public static function getMaxCreatePerHour(): int
{
if (self::$maxCreatePerHour === null) {
self::$maxCreatePerHour = config('instance.custom_filters.max_create_per_hour', 20);
}
return self::$maxCreatePerHour;
}
public static function getMaxUpdatesPerHour(): int
{
if (self::$maxUpdatesPerHour === null) {
self::$maxUpdatesPerHour = config('instance.custom_filters.max_updates_per_hour', 40);
}
return self::$maxUpdatesPerHour;
}
/**
* Get cached filters for an account with simplified, secure approach
*
@ -219,7 +289,7 @@ class CustomFilter extends Model
return;
}
$maxPatternsPerFilter = self::MAX_KEYWORDS_PER_FILTER ;
$maxPatternsPerFilter = self::getMaxFiltersPerUser() ;
$keywordsToProcess = $keywords->take($maxPatternsPerFilter);
$regexPatterns = $keywordsToProcess->map(function ($keyword) {
@ -237,7 +307,7 @@ class CustomFilter extends Model
}
$combinedPattern = implode('|', $regexPatterns);
$maxPatternLength = self::MAX_KEYWORD_LEN ;
$maxPatternLength = self::getMaxPatternLength() ;
if (strlen($combinedPattern) > $maxPatternLength) {
$combinedPattern = substr($combinedPattern, 0, $maxPatternLength);
}
@ -248,24 +318,24 @@ class CustomFilter extends Model
];
});
$statusFilters = CustomFilterStatus::with(['customFilter' => function ($query) use ($profileId) {
$query->unexpired()->where('profile_id', $profileId);
}])->get();
// $statusFilters = CustomFilterStatus::with(['customFilter' => function ($query) use ($profileId) {
// $query->unexpired()->where('profile_id', $profileId);
// }])->get();
$statusFilters->groupBy('custom_filter_id')->each(function ($statuses, $filterId) use (& $filtersHash) {
$filter = $statuses->first()->customFilter;
// $statusFilters->groupBy('custom_filter_id')->each(function ($statuses, $filterId) use (& $filtersHash) {
// $filter = $statuses->first()->customFilter;
if (! $filter) {
return;
}
// if (! $filter) {
// return;
// }
if (! isset($filtersHash[$filterId])) {
$filtersHash[$filterId] = ['filter' => $filter];
}
// if (! isset($filtersHash[$filterId])) {
// $filtersHash[$filterId] = ['filter' => $filter];
// }
$maxStatusIds = self::MAX_STATUSES_PER_FILTER;
$filtersHash[$filterId]['status_ids'] = $statuses->take($maxStatusIds)->pluck('status_id')->toArray();
});
// $maxStatusIds = self::MAX_STATUSES_PER_FILTER;
// $filtersHash[$filterId]['status_ids'] = $statuses->take($maxStatusIds)->pluck('status_id')->toArray();
// });
return array_map(function ($item) {
$filter = $item['filter'];
@ -300,7 +370,7 @@ class CustomFilter extends Model
if (isset($rules['keywords'])) {
$text = strip_tags($status['content']);
$maxContentLength = self::MAX_CONTENT_SCAN_LEN ;
$maxContentLength = self::getMaxContentScanLimit() ;
if (mb_strlen($text) > $maxContentLength) {
$text = mb_substr($text, 0, $maxContentLength);
}
@ -308,7 +378,7 @@ class CustomFilter extends Model
try {
preg_match_all($rules['keywords'], $text, $matches, PREG_PATTERN_ORDER, 0);
if (! empty($matches[0])) {
$maxReportedMatches = 10;
$maxReportedMatches = (int) config('instance.custom_filters.max_reported_matches', 10) ;
$keywordMatches = array_slice($matches[0], 0, $maxReportedMatches);
}
} catch (\Throwable $e) {
@ -318,15 +388,15 @@ class CustomFilter extends Model
}
}
if (isset($rules['status_ids'])) {
$statusId = $status->id;
$reblogId = $status->reblog_of_id ?? null;
// if (isset($rules['status_ids'])) {
// $statusId = $status->id;
// $reblogId = $status->reblog_of_id ?? null;
$matchingIds = array_intersect($rules['status_ids'], array_filter([$statusId, $reblogId]));
if (! empty($matchingIds)) {
$statusMatches = $matchingIds;
}
}
// $matchingIds = array_intersect($rules['status_ids'], array_filter([$statusId, $reblogId]));
// if (! empty($matchingIds)) {
// $statusMatches = $matchingIds;
// }
// }
if (! empty($keywordMatches) || ! empty($statusMatches)) {
$results[] = [