Aethel Bot OSS repository! aethel.xyz
bot fun ai discord discord-bot aethel
at dev 4.8 kB view raw
1import pgClient from './pgClient'; 2import logger from './logger'; 3 4export interface StrikeInfo { 5 strike_count: number; 6 banned_until: Date | null; 7} 8 9export class StrikeError extends Error { 10 constructor( 11 message: string, 12 public readonly userId?: string, 13 ) { 14 super(message); 15 this.name = 'StrikeError'; 16 } 17} 18 19export async function getUserStrikeInfo(userId: string): Promise<StrikeInfo | null> { 20 if (!userId || typeof userId !== 'string') { 21 throw new StrikeError('Invalid user ID provided'); 22 } 23 24 try { 25 const res = await pgClient.query( 26 'SELECT strike_count, banned_until FROM user_strikes WHERE user_id = $1', 27 [userId], 28 ); 29 30 if (res.rows.length === 0) { 31 return null; 32 } 33 34 return { 35 strike_count: res.rows[0].strike_count, 36 banned_until: res.rows[0].banned_until ? new Date(res.rows[0].banned_until) : null, 37 }; 38 } catch (error) { 39 logger.error('Failed to get user strike info', { userId, error }); 40 throw new StrikeError('Failed to retrieve strike information', userId); 41 } 42} 43 44export async function incrementUserStrike(userId: string): Promise<StrikeInfo> { 45 if (!userId || typeof userId !== 'string') { 46 throw new StrikeError('Invalid user ID provided'); 47 } 48 49 try { 50 const res = await pgClient.query( 51 ` 52 INSERT INTO user_strikes (user_id, strike_count, last_strike_at) 53 VALUES ($1, 1, NOW()) 54 ON CONFLICT (user_id) DO UPDATE SET 55 strike_count = user_strikes.strike_count + 1, 56 last_strike_at = NOW() 57 RETURNING strike_count, banned_until; 58 `, 59 [userId], 60 ); 61 62 if (res.rows.length === 0) { 63 throw new StrikeError('Failed to increment strike - no rows returned', userId); 64 } 65 66 const { strike_count, banned_until } = res.rows[0]; 67 68 if (strike_count >= 5 && (!banned_until || new Date(banned_until) < new Date())) { 69 try { 70 const banUntil = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); 71 await pgClient.query('UPDATE user_strikes SET banned_until = $2 WHERE user_id = $1', [ 72 userId, 73 banUntil, 74 ]); 75 76 logger.warn('User auto-banned due to strike limit', { 77 userId, 78 strikeCount: strike_count, 79 banUntil: banUntil.toISOString(), 80 }); 81 82 return { 83 strike_count, 84 banned_until: banUntil, 85 }; 86 } catch (banError) { 87 logger.error('Failed to apply auto-ban', { userId, error: banError }); 88 return { 89 strike_count, 90 banned_until: banned_until ? new Date(banned_until) : null, 91 }; 92 } 93 } 94 95 logger.info('User strike incremented', { userId, strikeCount: strike_count }); 96 97 return { 98 strike_count, 99 banned_until: banned_until ? new Date(banned_until) : null, 100 }; 101 } catch (error) { 102 logger.error('Failed to increment user strike', { userId, error }); 103 throw new StrikeError('Failed to increment strike count', userId); 104 } 105} 106 107export async function isUserBanned(userId: string): Promise<Date | null> { 108 if (!userId || typeof userId !== 'string') { 109 throw new StrikeError('Invalid user ID provided'); 110 } 111 112 try { 113 const res = await pgClient.query('SELECT banned_until FROM user_strikes WHERE user_id = $1', [ 114 userId, 115 ]); 116 117 if (res.rows.length === 0 || !res.rows[0].banned_until) { 118 return null; 119 } 120 121 const banUntil = new Date(res.rows[0].banned_until); 122 123 if (banUntil > new Date()) { 124 return banUntil; 125 } 126 127 return null; 128 } catch (error) { 129 logger.error('Failed to check user ban status', { userId, error }); 130 throw new StrikeError('Failed to check ban status', userId); 131 } 132} 133 134export async function resetOldStrikes(): Promise<number> { 135 try { 136 const res = await pgClient.query( 137 `UPDATE user_strikes 138 SET strike_count = 0, banned_until = NULL 139 WHERE last_strike_at < NOW() - INTERVAL '3 days' 140 RETURNING user_id`, 141 ); 142 143 const resetCount = res.rows.length; 144 145 if (resetCount > 0) { 146 logger.info('Reset old strikes', { resetCount }); 147 } 148 149 return resetCount; 150 } catch (error) { 151 logger.error('Failed to reset old strikes', { error }); 152 throw new StrikeError('Failed to reset old strikes'); 153 } 154} 155 156export async function clearUserStrikes(userId: string): Promise<boolean> { 157 if (!userId || typeof userId !== 'string') { 158 throw new StrikeError('Invalid user ID provided'); 159 } 160 161 try { 162 const res = await pgClient.query( 163 'UPDATE user_strikes SET strike_count = 0, banned_until = NULL WHERE user_id = $1', 164 [userId], 165 ); 166 167 logger.info('Cleared user strikes', { userId }); 168 return (res.rowCount ?? 0) > 0; 169 } catch (error) { 170 logger.error('Failed to clear user strikes', { userId, error }); 171 throw new StrikeError('Failed to clear strikes', userId); 172 } 173}