Aethel Bot OSS repository! aethel.xyz
bot fun ai discord discord-bot aethel
at main 168 lines 4.2 kB view raw
1import validator from 'validator'; 2import { ChatInputCommandInteraction } from 'discord.js'; 3import { UNALLOWED_WORDS } from '../constants/unallowedWords'; 4 5interface ValidationResult { 6 isValid: boolean; 7 message?: string; 8} 9 10function validateCommandOptions( 11 interaction: ChatInputCommandInteraction, 12 requiredOptions: string[] = [], 13): ValidationResult { 14 for (const option of requiredOptions) { 15 const value = interaction.options.getString(option); 16 if (!value || value.trim() === '') { 17 return { 18 isValid: false, 19 message: `Missing required option: ${option}`, 20 }; 21 } 22 } 23 return { isValid: true }; 24} 25 26function sanitizeInput(input?: string | null): string { 27 if (!input) return ''; 28 29 let sanitized = input 30 .replace(/[<>"'&]/g, '') 31 .replace(/javascript:/gi, '') 32 .replace(/data:/gi, '') 33 .replace(/vbscript:/gi, '') 34 .trim(); 35 36 sanitized = sanitized 37 .split('') 38 .filter((char) => { 39 const code = char.charCodeAt(0); 40 return code >= 32 && code !== 127 && (code < 128 || code > 159); 41 }) 42 .join(''); 43 44 return sanitized.substring(0, 1000); 45} 46 47function isValidUrl(url: string): boolean { 48 if (!url || typeof url !== 'string') return false; 49 50 try { 51 const parsedUrl = new URL(url); 52 53 if (!['http:', 'https:'].includes(parsedUrl.protocol)) { 54 return false; 55 } 56 57 const hostname = parsedUrl.hostname.toLowerCase(); 58 if ( 59 hostname === 'localhost' || 60 hostname === '127.0.0.1' || 61 hostname.startsWith('192.168.') || 62 hostname.startsWith('10.') || 63 hostname.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./) 64 ) { 65 return false; 66 } 67 68 return true; 69 } catch { 70 return false; 71 } 72} 73 74function validateTimeString(timeStr: string): boolean { 75 if (typeof timeStr !== 'string' || timeStr.length > 10) return false; 76 77 const timeRegex = /^(\d{1,3}h)?(\d{1,4}m)?$|^(\d{1,4}m)?(\d{1,3}h)?$/i; 78 79 if (!timeRegex.test(timeStr)) return false; 80 81 const hoursMatch = timeStr.match(/(\d+)h/i); 82 const minsMatch = timeStr.match(/(\d+)m/i); 83 84 const hours = hoursMatch ? parseInt(hoursMatch[1], 10) : 0; 85 const minutes = minsMatch ? parseInt(minsMatch[1], 10) : 0; 86 87 return hours <= 168 && minutes <= 10080; 88} 89 90function parseTimeString(timeStr: string): number | null { 91 if (!validateTimeString(timeStr)) return null; 92 93 let minutes = 0; 94 const hoursMatch = timeStr.match(/(\d+)h/i); 95 const minsMatch = timeStr.match(/(\d+)m/i); 96 97 if (hoursMatch) minutes += parseInt(hoursMatch[1], 10) * 60; 98 if (minsMatch) minutes += parseInt(minsMatch[1], 10); 99 100 return minutes > 0 ? minutes : null; 101} 102 103function formatTimeString(minutes: number): string { 104 if (!minutes || minutes < 0) return '0m'; 105 106 const hours = Math.floor(minutes / 60); 107 const mins = minutes % 60; 108 109 const parts: string[] = []; 110 if (hours > 0) parts.push(`${hours}h`); 111 if (mins > 0 || hours === 0) parts.push(`${mins}m`); 112 113 return parts.join(' '); 114} 115 116function isValidDomain(domain: string): boolean { 117 if (typeof domain !== 'string') return false; 118 return validator.isFQDN(domain, { require_tld: true }); 119} 120 121function _normalizeText(text: string): string { 122 if (!text) return ''; 123 124 return text 125 .toLowerCase() 126 .replace(/([a-z])\1{2,}/g, '$1') 127 .replace(/[^\w\s]/g, '') 128 .replace(/@/g, 'a') 129 .replace(/4/g, 'a') 130 .replace(/3/g, 'e') 131 .replace(/1|!/g, 'i') 132 .replace(/0/g, 'o') 133 .replace(/[5$]/g, 's') 134 .replace(/7/g, 't') 135 .trim(); 136} 137 138export function getUnallowedWordCategory(text: string): string | null { 139 if (!text || typeof text !== 'string') return null; 140 141 const words = text 142 .toLowerCase() 143 .split(/[\s"'.,?!;:]+/) 144 .map((word) => word.replace(/[^\w\s]/g, '')) 145 .filter((word) => word.length > 0); 146 147 for (const word of words) { 148 if (word.length <= 2) continue; 149 150 for (const [category, wordList] of Object.entries(UNALLOWED_WORDS)) { 151 if ((wordList as string[]).some((badWord) => word.toLowerCase() === badWord.toLowerCase())) { 152 return category; 153 } 154 } 155 } 156 157 return null; 158} 159 160export { 161 validateCommandOptions, 162 sanitizeInput, 163 validateTimeString, 164 parseTimeString, 165 formatTimeString, 166 isValidUrl, 167 isValidDomain, 168};