From f294462925383e3c66c3b4433ea98ebc0ec189c7 Mon Sep 17 00:00:00 2001 From: Hugh Date: Mon, 17 Nov 2025 11:43:54 -0800 Subject: [PATCH] Add URL validation and config file support - Added URL validation to prevent invalid URLs - Moved API key to config file for security - Added better error handling and retry logic - Auto-adds https:// protocol if missing - Enhanced user interface with clear messages - Updated README with documentation for both versions --- Url_Shortener/.gitignore | 6 + Url_Shortener/README.md | 70 +++++---- Url_Shortener/config.example.json | 3 + Url_Shortener/url_shortener_enhanced.py | 180 ++++++++++++++++++++++++ 4 files changed, 230 insertions(+), 29 deletions(-) create mode 100644 Url_Shortener/.gitignore create mode 100644 Url_Shortener/config.example.json create mode 100644 Url_Shortener/url_shortener_enhanced.py diff --git a/Url_Shortener/.gitignore b/Url_Shortener/.gitignore new file mode 100644 index 00000000..b6787eeb --- /dev/null +++ b/Url_Shortener/.gitignore @@ -0,0 +1,6 @@ +config.json +__pycache__/ +*.pyc +*.pyo +.DS_Store +url_history.db diff --git a/Url_Shortener/README.md b/Url_Shortener/README.md index 4e549931..d64ed564 100644 --- a/Url_Shortener/README.md +++ b/Url_Shortener/README.md @@ -14,53 +14,65 @@ - +A CLI URL shortener using the Bitly API. Now available in two versions: +- **Original** (`url_shortner.py`) - Simple and straightforward +- **Enhanced** (`url_shortener_enhanced.py`) - With validation, config file, and better error handling -## šŸ› ļø Description +## āš™ļø Languages or Frameworks Used - +Python 3.x with the following dependencies: -A cli url shortener. +```bash +pip install -r requirements.txt +``` - +## 🌟 How to Run -## āš™ļø Languages or Frameworks Used +### Option 1: Original Version - +1. Open `url_shortner.py` +2. Replace the `api_key` variable with your Bitly API key +3. Run: `python url_shortner.py` - - pip install -r requirements.txt +### Option 2: Enhanced Version (Recommended) +1. Get your Bitly API key from https://bitly.com/a/settings/api +2. Create config file: + ```bash + cp config.example.json config.json + ``` +3. Edit `config.json` and add your API key +4. Run: `python url_shortener_enhanced.py` -## 🌟 How to run +## ✨ Enhanced Version Features - +- āœ… **URL Validation** - Validates URLs before shortening to prevent errors +- āš™ļø **Config File** - Secure API key storage (not hardcoded) +- šŸ›”ļø **Better Error Handling** - Clear error messages with retry logic +- šŸ”„ **Auto-fix URLs** - Automatically adds `https://` if missing +- šŸ“Š **Enhanced UI** - Clean output with helpful messages +- šŸ”’ **Security** - Config file is gitignored to protect your API key -1. Replace the api_key in url_shortener.py to your bitly api key -2. Run the file !! +### Valid URL Examples +- `https://www.google.com` āœ… +- `google.com` āœ… (auto-adds https://) +- `http://example.com/long/path?query=value` āœ… - +### Invalid URLs (Caught by Validation) +- Empty input āŒ +- `not a url` āŒ +- `ftp://example.com` āŒ (wrong protocol) +- `https://` āŒ (no domain) ## šŸ“ŗ Demo ![alt text](assets/ezgif-5-7fc3e9b8f1.gif) - - -## šŸ¤– Author - - +## šŸ¤– Authors -[dongjin2008](https://github.com/dongjin2008) \ No newline at end of file +- Original: [dongjin2008](https://github.com/dongjin2008) +- Enhanced version: [Your GitHub Username] diff --git a/Url_Shortener/config.example.json b/Url_Shortener/config.example.json new file mode 100644 index 00000000..c33b9660 --- /dev/null +++ b/Url_Shortener/config.example.json @@ -0,0 +1,3 @@ +{ + "bitly_api_key": "YOUR_API_KEY_HERE" +} diff --git a/Url_Shortener/url_shortener_enhanced.py b/Url_Shortener/url_shortener_enhanced.py new file mode 100644 index 00000000..75513be2 --- /dev/null +++ b/Url_Shortener/url_shortener_enhanced.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +""" +Enhanced URL Shortener +Adds URL validation, config file support, and better error handling +""" + +import requests +import json +import sys +import re +from urllib.parse import urlparse +from pathlib import Path + + +class URLShortener: + """URL shortener with validation and configuration support""" + + def __init__(self, config_path='config.json'): + """Initialize with configuration""" + self.config = self.load_config(config_path) + self.api_key = self.config.get('bitly_api_key') + + if not self.api_key or self.api_key == 'YOUR_API_KEY_HERE': + print("āš ļø Error: Please set your Bitly API key in config.json") + print(" Get your API key from: https://bitly.com/a/settings/api") + sys.exit(1) + + def load_config(self, config_path): + """Load configuration from JSON file""" + try: + with open(config_path, 'r') as f: + return json.load(f) + except FileNotFoundError: + print(f"āš ļø Config file '{config_path}' not found!") + print(" Creating config.example.json - please rename and add your API key") + sys.exit(1) + except json.JSONDecodeError: + print(f"āš ļø Invalid JSON in '{config_path}'") + sys.exit(1) + + def validate_url(self, url): + """ + Validate URL format and structure + + Args: + url (str): URL to validate + + Returns: + tuple: (is_valid, error_message) + """ + # Check if empty + if not url or not url.strip(): + return False, "URL cannot be empty" + + url = url.strip() + + # Add scheme if missing + if not url.startswith(('http://', 'https://')): + url = 'https://' + url + + # Parse URL + try: + parsed = urlparse(url) + + # Check for valid scheme + if parsed.scheme not in ['http', 'https']: + return False, "URL must use http or https protocol" + + # Check for valid domain + if not parsed.netloc: + return False, "URL must have a valid domain" + + # Basic domain validation (has at least one dot) + if '.' not in parsed.netloc: + return False, "URL must have a valid domain (e.g., example.com)" + + # Check domain doesn't have invalid characters + domain_pattern = r'^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$' + domain = parsed.netloc.split(':')[0] # Remove port if present + + if not re.match(domain_pattern, domain): + return False, "Invalid domain format" + + return True, url + + except Exception as e: + return False, f"Invalid URL format: {str(e)}" + + def shorten_url(self, long_url): + """ + Shorten a URL using Bitly API + + Args: + long_url (str): URL to shorten + + Returns: + tuple: (success, result_or_error_message) + """ + # Validate URL first + is_valid, result = self.validate_url(long_url) + if not is_valid: + return False, result + + # Use the validated/normalized URL + validated_url = result + + headers = { + 'Authorization': f'Bearer {self.api_key}', + 'Content-Type': 'application/json', + } + + data = {"long_url": validated_url} + + # Try up to 3 times + for attempt in range(3): + try: + response = requests.post( + "https://api-ssl.bitly.com/v4/shorten", + headers=headers, + data=json.dumps(data), + timeout=10 + ) + + if response.status_code == 200: + link = response.json()['link'] + return True, link + elif response.status_code == 403: + return False, "Invalid API key. Please check your config.json" + elif response.status_code == 400: + error_msg = response.json().get('message', 'Bad request') + return False, f"Invalid request: {error_msg}" + else: + if attempt == 2: # Last attempt + return False, f"API error (HTTP {response.status_code})" + + except requests.exceptions.Timeout: + if attempt == 2: + return False, "Request timed out after 3 attempts" + except requests.exceptions.ConnectionError: + if attempt == 2: + return False, "Connection error. Check your internet connection" + except Exception as e: + if attempt == 2: + return False, f"Unexpected error: {str(e)}" + + return False, "Failed after 3 attempts" + + +def main(): + """Main function to run the URL shortener""" + print("=" * 50) + print("šŸ”— Enhanced URL Shortener") + print("=" * 50) + + # Initialize shortener + shortener = URLShortener() + + # Get user input + long_url = input("\nšŸ“Ž Enter the long URL: ").strip() + + if not long_url: + print("āŒ No URL provided!") + return + + print("\nā³ Shortening URL...") + + # Shorten the URL + success, result = shortener.shorten_url(long_url) + + if success: + print(f"\nāœ… Success!") + print(f"šŸ“‹ Shortened URL: {result}") + print(f"\nšŸ’” Copy and share: {result}") + else: + print(f"\nāŒ Error: {result}") + sys.exit(1) + + +if __name__ == "__main__": + main()