Aethel Bot OSS repository! aethel.xyz
bot fun ai discord discord-bot aethel
at dev 4.1 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 normalizeInput(text: string): string { 122 let normalized = text.toLowerCase(); 123 normalized = normalized.replace(/([a-z])\1{2,}/g, '$1'); 124 normalized = normalized 125 .replace(/[@4]/g, 'a') 126 .replace(/[3]/g, 'e') 127 .replace(/[1!]/g, 'i') 128 .replace(/[0]/g, 'o') 129 .replace(/[5$]/g, 's') 130 .replace(/[7]/g, 't'); 131 return normalized; 132} 133 134export function getUnallowedWordCategory(text: string): string | null { 135 const normalized = normalizeInput(text); 136 for (const [category, words] of Object.entries(UNALLOWED_WORDS)) { 137 for (const word of words as string[]) { 138 if (category === 'slurs') { 139 if (normalized.includes(word)) { 140 return category; 141 } 142 } else { 143 const pattern = new RegExp(`(?:^|\\W)${word}[a-z]{0,2}(?:\\W|$)`, 'i'); 144 if (pattern.test(normalized)) { 145 return category; 146 } 147 } 148 } 149 } 150 return null; 151} 152 153export { 154 validateCommandOptions, 155 sanitizeInput, 156 validateTimeString, 157 parseTimeString, 158 formatTimeString, 159 isValidUrl, 160 isValidDomain, 161};