Files
5chan/src/lib/utils/pattern-utils.ts
T

111 lines
4.3 KiB
TypeScript

import { Comment } from '@plebbit/plebbit-react-hooks';
/**
* Checks if a text matches a pattern according to various pattern matching rules:
* - Whole word matching: 'feel' matches 'feel' but not 'feeling'
* - AND operator: 'feel girlfriend' matches both words in any order
* - OR operator: 'feel|girlfriend' matches either word
* - Mixed operators: 'girlfriend|boyfriend feel' matches complex logic
* - Exact match: "that feel when" matches the exact phrase
* - Wildcards: 'feel*' matches 'feel', 'feels', 'feeling', etc.
* - Regex: /pattern/flags for regular expressions
*
* @param text The text to check against the pattern
* @param pattern The pattern to match
* @returns True if the text matches the pattern, false otherwise
*/
export const matchesPattern = (text: string, pattern: string): boolean => {
if (!pattern || !text) return false;
const textLower = text.toLowerCase();
try {
// Check if it's a regex pattern (starts and ends with /)
if (pattern.startsWith('/') && pattern.length > 2 && /\/[gimsuy]*$/.test(pattern)) {
const lastSlashIndex = pattern.lastIndexOf('/');
const regexPattern = pattern.substring(1, lastSlashIndex);
const flags = pattern.substring(lastSlashIndex + 1);
const regex = new RegExp(regexPattern, flags);
return regex.test(textLower);
}
// Check if it's an exact match pattern (surrounded by quotes)
else if (pattern.startsWith('"') && pattern.endsWith('"') && pattern.length > 2) {
const exactPattern = pattern.substring(1, pattern.length - 1).toLowerCase();
return textLower.includes(exactPattern);
}
// Check for OR patterns (contains |)
else if (pattern.includes('|')) {
const orGroups = pattern.split(' ').filter(Boolean);
// Process each group (which might contain OR operators)
return orGroups.every((group) => {
const orTerms = group.split('|').map((term) => term.toLowerCase());
return orTerms.some((term) => {
// Handle wildcards in OR terms
if (term.includes('*')) {
const regexPattern = term.replace(/\*/g, '.*').toLowerCase();
const regex = new RegExp(`\\b${regexPattern}\\b`, 'i');
return regex.test(textLower);
} else {
// Match whole word only
const regex = new RegExp(`\\b${term}\\b`, 'i');
return regex.test(textLower);
}
});
});
}
// Check for AND patterns (space-separated words)
else if (pattern.includes(' ')) {
const andTerms = pattern
.split(' ')
.filter(Boolean)
.map((term) => term.toLowerCase());
return andTerms.every((term) => {
// Handle wildcards in AND terms
if (term.includes('*')) {
const regexPattern = term.replace(/\*/g, '.*').toLowerCase();
const regex = new RegExp(`\\b${regexPattern}\\b`, 'i');
return regex.test(textLower);
} else {
// Match whole word only
const regex = new RegExp(`\\b${term}\\b`, 'i');
return regex.test(textLower);
}
});
}
// Handle wildcard patterns
else if (pattern.includes('*')) {
const regexPattern = pattern.replace(/\*/g, '.*').toLowerCase();
const regex = new RegExp(`\\b${regexPattern}\\b`, 'i');
return regex.test(textLower);
}
// Simple whole word match
else {
const regex = new RegExp(`\\b${pattern.toLowerCase()}\\b`, 'i');
return regex.test(textLower);
}
} catch (error) {
// If regex parsing fails, fall back to simple includes
console.error('Pattern matching error:', error);
const simplifiedPattern = pattern.toLowerCase();
return textLower.includes(simplifiedPattern);
}
};
/**
* Checks if a comment matches a pattern in its title or content
*
* @param comment The comment to check
* @param pattern The pattern to match
* @returns True if the comment matches the pattern, false otherwise
*/
export const commentMatchesPattern = (comment: Comment, pattern: string): boolean => {
if (!pattern || !comment) return false;
const titleLower = comment?.title?.toLowerCase() || '';
const contentLower = comment?.content?.toLowerCase() || '';
const textToMatch = titleLower + ' ' + contentLower;
return matchesPattern(textToMatch, pattern);
};