diff --git a/joke-generator/README.md b/joke-generator/README.md new file mode 100644 index 0000000..c105083 --- /dev/null +++ b/joke-generator/README.md @@ -0,0 +1,227 @@ +# šŸ˜„ Random Joke Generator + +A complete, production-ready joke generator that fetches random jokes from an external API (JokeAPI). Includes implementations in Python and JavaScript with a beautiful web UI. + +## šŸ“‹ Features + +✨ **Multiple Implementations** +- Python CLI with interactive menu +- JavaScript with async/await +- Responsive HTML5 web interface + +āœ… **Joke Categories** +- Any (random joke) +- General +- Programming +- Knock-Knock + +šŸ›”ļø **Robust Error Handling** +- Network timeout handling +- Connection error recovery +- Graceful failure messages + +šŸŽØ **Beautiful UI** +- Gradient design +- Smooth animations +- Mobile responsive +- Punchline reveal functionality + +šŸ“” **External API Integration** +- Uses [JokeAPI](https://jokeapi.dev/) +- Free, no authentication required +- Fast and reliable + +## šŸš€ Quick Start + +### Python CLI + +**Requirements:** +```bash +pip install requests +``` + +**Usage:** +```bash +python joke_generator.py +``` + +**Features:** +- Interactive menu system +- Press Enter to reveal punchlines +- Safe mode enabled by default +- Session management with connection pooling + +### Web UI + +**Usage:** +Simply open `joke_generator.html` in any modern web browser! + +**Features:** +- Click "Get a Joke" to fetch a random joke +- Select joke type from dropdown +- Press Enter key as shortcut +- Automatic punchline reveal button for two-part jokes + +### JavaScript Module + +**Usage:** +```javascript +const generator = new JokeGeneratorUI(); +generator.init(); // Initialize event listeners + +// Or fetch directly +const joke = await generator.fetchJoke("programming", true); +generator.displayJoke(joke); +``` + +## šŸ“š API Reference + +### JokeAPI Endpoints + +**Get Any Joke:** +``` +GET https://v2.jokeapi.dev/joke/any?safe-mode=true +``` + +**Get Programming Joke:** +``` +GET https://v2.jokeapi.dev/joke/programming?safe-mode=true +``` + +**Response Format (Single-part):** +```json +{ + "type": "single", + "joke": "Why do Java developers wear glasses? Because they don't C#", + "id": 1 +} +``` + +**Response Format (Two-part):** +```json +{ + "type": "twopart", + "setup": "Why did the scarecrow win an award?", + "delivery": "He was outstanding in his field!", + "id": 2 +} +``` + +## šŸ’» Code Examples + +### Python - Simple Usage + +```python +from joke_generator import JokeGenerator + +# Create instance +gen = JokeGenerator() + +# Fetch and display a joke +joke = gen.get_joke("programming", safe_mode=True) +if joke: + gen.display_joke(joke) +``` + +### Python - Custom Implementation + +```python +from joke_generator import JokeGenerator + +gen = JokeGenerator(timeout=15) +joke_data = gen.get_joke("knock-knock") + +if joke_data["type"] == "twopart": + print(joke_data["setup"]) + input("Press Enter for the punchline...") + print(joke_data["delivery"]) +``` + +### JavaScript - Fetch Joke + +```javascript +const ui = new JokeGeneratorUI(); + +// Fetch a programming joke +const joke = await ui.fetchJoke("programming", true); + +// Display it +ui.displayJoke(joke); +``` + +## šŸ”§ Customization + +### Change API Endpoint + +**Python:** +```python +generator.BASE_URL = "https://custom-api.com/jokes" +``` + +**JavaScript:** +```javascript +generator.baseUrl = "https://custom-api.com/jokes"; +``` + +### Disable Safe Mode + +**Python:** +```python +joke = generator.get_joke("any", safe_mode=False) +``` + +**JavaScript:** +```javascript +const joke = await generator.fetchJoke("any", false); +``` + +### Adjust Timeout + +**Python:** +```python +generator = JokeGenerator(timeout=20) +``` + +**JavaScript:** +```javascript +generator.timeout = 20000; // milliseconds +``` + +## 🌟 Future Enhancements + +- [ ] Save favorite jokes to local storage +- [ ] Share jokes on social media +- [ ] Rate jokes (like/dislike) +- [ ] Search by keyword +- [ ] Multiple language support +- [ ] Daily joke email notifications +- [ ] Dark mode toggle +- [ ] Accessibility improvements (WCAG 2.1) + +## šŸ“ File Structure + +``` +joke-generator/ +ā”œā”€ā”€ joke_generator.py # Python CLI implementation +ā”œā”€ā”€ joke_generator.js # JavaScript module +ā”œā”€ā”€ joke_generator.html # Web UI +└── README.md # Documentation +``` + +## šŸ”— Resources + +- [JokeAPI Documentation](https://jokeapi.dev/) +- [Python Requests Library](https://requests.readthedocs.io/) +- [MDN Web Docs - Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) + +## šŸ“„ License + +This project is open source and available under the MIT License. + +## šŸ¤ Contributing + +Feel free to fork, modify, and use this project as you wish. Suggestions for improvements are always welcome! + +--- + +**Made with ā¤ļø for the developer community** diff --git a/joke-generator/joke_generator.html b/joke-generator/joke_generator.html new file mode 100644 index 0000000..ba5b296 --- /dev/null +++ b/joke-generator/joke_generator.html @@ -0,0 +1,220 @@ + + + + + + Random Joke Generator + + + +
+
+

šŸ˜„ Joke Generator

+

Get random jokes instantly!

+
+ +
+ + +
+ +
+
+ +
+ + +
+ + + + diff --git a/joke-generator/joke_generator.js b/joke-generator/joke_generator.js new file mode 100644 index 0000000..23a2ce8 --- /dev/null +++ b/joke-generator/joke_generator.js @@ -0,0 +1,192 @@ +/** + * Random Joke Generator - JavaScript Implementation + * Fetches jokes from JokeAPI and displays them with smooth animations + * Includes XSS protection and comprehensive error handling + */ + +class JokeGeneratorUI { + constructor() { + this.baseUrl = "https://v2.jokeapi.dev/joke"; + this.timeout = 10000; // milliseconds + this.jokeTypes = ["any", "general", "programming", "knock-knock"]; + this.currentJoke = null; + this.isLoading = false; + } + + /** + * Initialize event listeners + */ + init() { + const getJokeBtn = document.getElementById("get-joke-btn"); + const punchlineBtn = document.getElementById("punchline-btn"); + const jokeTypeSelect = document.getElementById("joke-type"); + + if (getJokeBtn) { + getJokeBtn.addEventListener("click", () => this.handleGetJoke()); + if (jokeTypeSelect) { + jokeTypeSelect.addEventListener("keypress", (e) => { + if (e.key === "Enter") this.handleGetJoke(); + }); + } + } + + if (punchlineBtn) { + punchlineBtn.addEventListener("click", () => this.showPunchline()); + } + } + + /** + * Escape HTML to prevent XSS attacks + */ + escapeHtml(text) { + const map = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + }; + return text.replace(/[&<>"']/g, (m) => map[m]); + } + + /** + * Fetch a joke from the API + */ + async fetchJoke(jokeType = "any", safeMode = true) { + try { + if (!this.jokeTypes.includes(jokeType.toLowerCase())) { + console.error(`Invalid joke type: ${jokeType}`); + return null; + } + + const url = new URL(`${this.baseUrl}/${jokeType}`); + url.searchParams.append("safe-mode", safeMode.toString().toLowerCase()); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), this.timeout); + + const response = await fetch(url.toString(), { + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + throw new Error( + `HTTP Error: ${response.status} ${response.statusText}` + ); + } + + const data = await response.json(); + return data; + } catch (error) { + if (error.name === "AbortError") { + this.showError("Request timed out. Please try again."); + } else if (error instanceof TypeError) { + this.showError( + "Network error. Please check your internet connection." + ); + } else { + this.showError("Failed to fetch joke. Please try again."); + } + return null; + } + } + + /** + * Display the joke in the UI + */ + displayJoke(jokeData) { + if (!jokeData || jokeData.error) { + this.showError("No joke received. Please try again."); + return; + } + + this.currentJoke = jokeData; + const jokeContainer = document.getElementById("joke-container"); + const jokeText = document.getElementById("joke-text"); + const punchlineBtn = document.getElementById("punchline-btn"); + + jokeContainer.style.display = "block"; + + if (jokeData.type === "single") { + jokeText.textContent = this.escapeHtml(jokeData.joke); + punchlineBtn.style.display = "none"; + } else if (jokeData.type === "twopart") { + jokeText.textContent = this.escapeHtml(jokeData.setup); + punchlineBtn.style.display = "block"; + punchlineBtn.textContent = "Show Punchline šŸ˜‚"; + punchlineBtn.dataset.punchline = this.escapeHtml( + jokeData.delivery + ); + } + + this.isLoading = false; + this.updateButtonState(); + } + + /** + * Show the punchline for two-part jokes + */ + showPunchline() { + const jokeText = document.getElementById("joke-text"); + const punchlineBtn = document.getElementById("punchline-btn"); + + if (punchlineBtn.dataset.punchline) { + jokeText.textContent = punchlineBtn.dataset.punchline; + punchlineBtn.style.display = "none"; + } + } + + /** + * Show error message in the joke container + */ + showError(message) { + const jokeContainer = document.getElementById("joke-container"); + const jokeText = document.getElementById("joke-text"); + const punchlineBtn = document.getElementById("punchline-btn"); + + jokeContainer.style.display = "block"; + jokeText.textContent = `Error: ${message}`; + punchlineBtn.style.display = "none"; + + this.isLoading = false; + this.updateButtonState(); + } + + /** + * Update button state based on loading status + */ + updateButtonState() { + const getJokeBtn = document.getElementById("get-joke-btn"); + if (getJokeBtn) { + getJokeBtn.disabled = this.isLoading; + getJokeBtn.textContent = this.isLoading ? "Loading..." : "Get a Joke!"; + } + } + + /** + * Handle the Get Joke button click + */ + async handleGetJoke() { + if (this.isLoading) return; + + this.isLoading = true; + this.updateButtonState(); + + const jokeTypeSelect = document.getElementById("joke-type"); + const selectedType = jokeTypeSelect.value; + + const jokeData = await this.fetchJoke(selectedType, true); + + if (jokeData) { + this.displayJoke(jokeData); + } + } +} + +// Initialize when DOM is ready +document.addEventListener("DOMContentLoaded", () => { + const generator = new JokeGeneratorUI(); + generator.init(); +}); diff --git a/joke-generator/joke_generator.py b/joke-generator/joke_generator.py new file mode 100644 index 0000000..fe8a596 --- /dev/null +++ b/joke-generator/joke_generator.py @@ -0,0 +1,165 @@ +""" +Random Joke Generator using JokeAPI +A simple yet powerful joke generator that fetches jokes from an external API. +Supports multiple joke types and provides a clean, interactive CLI experience. +""" + +import requests +import json +from typing import Dict, Optional +import sys + + +class JokeGenerator: + """ + A class to generate random jokes using the JokeAPI. + Provides methods to fetch and display jokes in various formats. + """ + + BASE_URL = "https://v2.jokeapi.dev/joke" + JOKE_TYPES = { + "any": "Any", + "general": "General", + "programming": "Programming", + "knock-knock": "Knock-Knock" + } + + def __init__(self, timeout: int = 10): + """ + Initialize the JokeGenerator. + + Args: + timeout (int): Request timeout in seconds. Default is 10. + """ + self.timeout = timeout + self.session = requests.Session() + + def get_joke(self, joke_type: str = "any", safe_mode: bool = True) -> Optional[Dict]: + """ + Fetch a random joke from JokeAPI. + + Args: + joke_type (str): Type of joke ('any', 'general', 'programming', 'knock-knock') + safe_mode (bool): If True, filters out offensive jokes + + Returns: + Optional[Dict]: Joke data dictionary or None if request fails + """ + try: + # Validate joke type + if joke_type.lower() not in self.JOKE_TYPES: + print(f"Invalid joke type: {joke_type}") + print(f"Available types: {', '.join(self.JOKE_TYPES.keys())}") + return None + + # Build API URL + url = f"{self.BASE_URL}/{joke_type}" + params = {"safe-mode": str(safe_mode).lower()} + + # Make request + response = self.session.get(url, params=params, timeout=self.timeout) + response.raise_for_status() + + return response.json() + + except requests.exceptions.Timeout: + print("Error: Request timed out. Please try again.") + return None + except requests.exceptions.ConnectionError: + print("Error: Failed to connect to JokeAPI. Check your internet connection.") + return None + except requests.exceptions.HTTPError as e: + print(f"Error: HTTP {e.response.status_code} - {e.response.reason}") + return None + except Exception as e: + print(f"Error: An unexpected error occurred - {str(e)}") + return None + + def display_joke(self, joke_data: Dict) -> None: + """ + Display a joke in a formatted manner. + + Args: + joke_data (Dict): Joke data from the API + """ + if not joke_data or joke_data.get("error"): + print("No joke received. Please try again.") + return + + print("\n" + "=" * 60) + print("šŸŽ­ JOKE TIME! šŸŽ­") + print("=" * 60) + + if joke_data["type"] == "single": + # Single-part joke + print(f"\n{joke_data['joke']}\n") + else: + # Two-part joke + print(f"\n{joke_data['setup']}\n") + input("Press Enter to reveal the punchline...\n") + print(f"{joke_data['delivery']}\n") + + print("=" * 60 + "\n") + + def interactive_menu(self) -> None: + """Display an interactive menu for selecting and fetching jokes.""" + while True: + print("\nšŸŽŖ RANDOM JOKE GENERATOR šŸŽŖ") + print("-" * 40) + print("Select a joke type:") + print() + + for idx, (key, display_name) in enumerate(self.JOKE_TYPES.items(), 1): + print(f" {idx}. {display_name}") + + print(f" {len(self.JOKE_TYPES) + 1}. Exit") + print() + + try: + choice = input("Enter your choice (1-5): ").strip() + + if choice == str(len(self.JOKE_TYPES) + 1): + print("\nšŸ‘‹ Thanks for laughing with us! Goodbye!\n") + break + + if choice not in ["1", "2", "3", "4"]: + print("Invalid choice. Please try again.") + continue + + joke_type_list = list(self.JOKE_TYPES.keys()) + selected_type = joke_type_list[int(choice) - 1] + + print("\nšŸ“” Fetching your joke...") + joke_data = self.get_joke(selected_type, safe_mode=True) + + if joke_data: + self.display_joke(joke_data) + + another = ( + input("Would you like another joke? (y/n): ") + .strip() + .lower() + ) + if another != "y": + print("\nšŸ‘‹ Thanks for laughing with us! Goodbye!\n") + break + + except KeyboardInterrupt: + print("\n\nšŸ‘‹ Goodbye!\n") + break + except Exception as e: + print(f"An error occurred: {e}") + + +def main(): + """Main entry point for the joke generator.""" + try: + generator = JokeGenerator() + generator.interactive_menu() + except KeyboardInterrupt: + print("\n\nGoodbye!") + sys.exit(0) + + +if __name__ == "__main__": + main()