import type { TimePeriod } from './discord/types.ts'; export interface RedditPost { id: string; title: string; author: string; score: number; num_comments: number; created_utc: number; permalink: string; url: string; selftext: string; is_gallery?: boolean; over_18: boolean; link_flair_text?: string; thumbnail?: string; preview?: { images: Array<{ source: { url: string; width: number; height: number; }; resolutions: Array<{ url: string; width: number; height: number; }>; }>; enabled: boolean; }; media?: { reddit_video?: { fallback_url: string; }; }; secure_media?: { reddit_video?: { fallback_url: string; }; }; } export interface RedditResponse { data: { children: Array<{ data: RedditPost; }>; }; } type SortType = 'hot' | 'top'; async function fetchWithRetry( url: string, maxRetries: number = 3, ): Promise { for (let attempt = 0; attempt < maxRetries; attempt++) try { await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000 + 500), ); const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Accept-Encoding': 'gzip, deflate, br', DNT: '1', Connection: 'keep-alive', 'Upgrade-Insecure-Requests': '1', }, }); return response; } catch (error) { if (attempt === maxRetries - 1) throw error; const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000; console.log(`Attempt ${attempt + 1} failed, retrying in ${delay}ms ...`); await new Promise((resolve) => setTimeout(resolve, delay)); } throw new Error('Max retries exceeded'); } export async function fetchRedditPosts( sort: SortType = 'hot', time: TimePeriod = 'day', ): Promise { const url = `https://www.reddit.com/r/okbuddyumamusume/${sort}.json${sort === 'top' ? `?t=${time}` : ''}`; const response = await fetchWithRetry(url); if (!response.ok) { let errorText = `Error fetching ${response.url}: ${response.status} ${response.statusText}`; try { const error = await response.text(); if ( error.includes("You've been blocked by network security") || error.includes('blocked by network security') ) throw new Error( 'Reddit is blocking requests due to network security. This may be due to rate limiting or bot detection. Please try again later.', ); if (error) errorText = `${errorText} \n\n ${error}`; } catch (err) { if ( err instanceof Error && err.message.includes('blocked by network security') ) throw err; } throw new Error(errorText); } const data: RedditResponse = await response.json(); return data.data.children.map((post) => post.data); } export function filterPostsByFlair( posts: RedditPost[], excludedFlairs: string[] = [], includedFlairs: string[] = [], ): RedditPost[] { return posts.filter((post) => { if (post.is_gallery) return false; const hasMedia = post.media?.reddit_video?.fallback_url || post.secure_media?.reddit_video?.fallback_url || post.url; if (!hasMedia) return false; const postFlair = post.link_flair_text?.toLowerCase() || ''; const isNSFW = post.over_18 || postFlair.includes('nsfw'); if ( includedFlairs.length > 0 && includedFlairs.some((flair) => flair.toLowerCase() === 'nsfw') ) if (includedFlairs.some((flair) => flair.toLowerCase() === 'nsfw')) return isNSFW; if (isNSFW) return false; if (includedFlairs.length > 0) return includedFlairs.some((flair) => postFlair.includes(flair.toLowerCase()), ); if (excludedFlairs.length > 0) return !excludedFlairs.some((flair) => postFlair.includes(flair.toLowerCase()), ); return true; }); } function getRandomPost(posts: RedditPost[]): RedditPost { if (posts.length === 0) throw new Error('No posts found matching the criteria'); const randomIndex = Math.floor(Math.random() * posts.length); return posts[randomIndex]; } export async function getCutePost(): Promise { const posts = await fetchRedditPosts('hot'); const filteredPosts = filterPostsByFlair(posts, ['roleplay', 'announcement']); return getRandomPost(filteredPosts); } export async function getRoleplayPost(): Promise { const posts = await fetchRedditPosts('hot'); const filteredPosts = filterPostsByFlair(posts, [], ['roleplay']); return getRandomPost(filteredPosts); } export async function getNSFWPost(): Promise { const posts = await fetchRedditPosts('hot'); const filteredPosts = filterPostsByFlair(posts, [], ['nsfw']); return getRandomPost(filteredPosts); } export async function getTopPost( time: TimePeriod = 'day', ): Promise { const posts = await fetchRedditPosts('top', time); const filteredPosts = filterPostsByFlair(posts, ['roleplay', 'announcement']); return getRandomPost(filteredPosts); }