Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Url_Shortener/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
config.json
__pycache__/
*.pyc
*.pyo
.DS_Store
url_history.db
70 changes: 41 additions & 29 deletions Url_Shortener/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,65 @@



<!--An image is an illustration for your project, the tip here is using your sense of humour as much as you can :D



You can copy paste my markdown photo insert as following:

<p align="center">

<img src="your-source-is-here" width=40% height=40%>
## 🛠️ Description

-->
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

<!--Remove the below lines and add yours -->
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

<!--Remove the below lines and add yours -->
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

<!--Remove the below lines and add yours -->
- ✅ **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

<!--Remove the below lines and add yours -->
## 🤖 Authors

[dongjin2008](https://github.com/dongjin2008)
- Original: [dongjin2008](https://github.com/dongjin2008)
- Enhanced version: [Your GitHub Username]
3 changes: 3 additions & 0 deletions Url_Shortener/config.example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"bitly_api_key": "YOUR_API_KEY_HERE"
}
180 changes: 180 additions & 0 deletions Url_Shortener/url_shortener_enhanced.py
Original file line number Diff line number Diff line change
@@ -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()