Skip to content

Instantly share code, notes, and snippets.

@vbresan
Created December 22, 2025 16:17
Show Gist options
  • Select an option

  • Save vbresan/a295a1b75dd58e65f1146a7e9f49ba40 to your computer and use it in GitHub Desktop.

Select an option

Save vbresan/a295a1b75dd58e65f1146a7e9f49ba40 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
Minimal Nautilus extension to convert dropped URL shortcuts.
Logs to /tmp/nautilus-drop-test.log
Installation:
1. Install nautilus-python: sudo apt install python3-nautilus
2. Copy this file to: ~/.local/share/nautilus-python/extensions/shortcut_converter.py
3. Restart Nautilus: nautilus -q
4. Optionally watch the log: tail -f /tmp/nautilus-shortcut-converter.log
Then try dragging a link from Firefox into a folder.
"""
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Nautilus, GObject
from urllib.parse import unquote
import datetime
import os
import re
import urllib.request
LOG_FILE = "/tmp/nautilus-shortcut-converter.log"
def log(message: str) -> None:
"""Write a message to the log file"""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(LOG_FILE, "a") as f:
f.write(f"[{timestamp}] {message}\n")
def get_filepath(uri: str) -> str:
"""Decode the URI to get the actual file path"""
try:
return unquote(uri.replace("file://", ""))
except:
return uri.replace("file://", "")
def is_link(filename: str) -> bool:
"""Check if the filename indicates a dropped link"""
return filename.startswith(("http:", "https:")) and filename.endswith(".txt")
def get_website_title(url: str, timeout: int = 5) -> str:
"""Fetch the title of a website from its URL"""
try:
# Set a user agent to avoid being blocked
req = urllib.request.Request(
url,
headers={"User-Agent": "Mozilla/5.0"}
)
# Fetch the page
with urllib.request.urlopen(req, timeout=timeout) as response:
html = response.read().decode("utf-8", errors="ignore")
# Extract title using regex
match = re.search(r"<title[^>]*>(.*?)</title>", html, re.IGNORECASE | re.DOTALL)
if match:
title = match.group(1).strip()
title = re.sub(r"\s+", " ", title)
return title
return None
except Exception as e:
return None
def remove_head(filename: str) -> str:
"""Remove leading http:-- or https:-- from the filename"""
if filename.startswith("https:--"):
return filename[8:]
elif filename.startswith("http:--"):
return filename[7:]
else:
return filename
def get_windows_filename(filename: str) -> str:
"""Convert filename to Windows-compatible format"""
name, ext = os.path.splitext(filename)
name = re.sub(r"[<>:\"/\\|?*]", "-", name)
name = re.sub("-+", "-", name)
if len(name) > 200:
name = name[:200]
name = name.strip(". -")
return name + ext
def get_unique_filepath(filepath: str, filename: str) -> str:
"""Generate a unique filepath by appending (N) if the file exists"""
directory = os.path.dirname(filepath)
new_filepath = os.path.join(directory, filename)
# Avoid overwriting existing files
counter = 2
while os.path.exists(new_filepath):
name, ext = os.path.splitext(filename)
numbered_filename = f"{name} ({counter}){ext}"
new_filepath = os.path.join(directory, numbered_filename)
counter += 1
return new_filepath
class ShortcutConverterExtension(GObject.GObject, Nautilus.MenuProvider):
"""Nautilus extension to convert dropped browser links (.txt) to Windows .url shortcuts.
Processes files on selection, logs actions, and modifies matching files.
"""
def __init__(self) -> None:
"""Initialize the extension and log startup."""
super().__init__()
log("=" * 60)
log("ShortcutConverterExtension initialized")
def get_file_items(self, files: list[Nautilus.FileInfo]) -> list[Nautilus.MenuItem]:
"""Process selected files for link conversion (returns empty list as no menu items are added)"""
for file_info in files:
filepath = get_filepath(file_info.get_uri())
filename = os.path.basename(filepath)
if not is_link(filename):
continue
log(f"--- NEW LINK DROPPED ---")
log(f"Filename: {filename}")
log(f"Path : {os.path.dirname(filepath)}")
# Try to read and update file content
if os.path.isfile(filepath):
try:
with open(filepath, "r+", encoding="utf-8", errors="ignore") as f:
content = f.read().strip()
new_content = f"[InternetShortcut]\r\nURL={content}\r\n"
f.seek(0)
f.write(new_content)
log(f"Content replaced with Internet Shortcut format")
except Exception as e:
log(f"Error reading file: {e}")
else:
log("(Not a regular file or cannot access)")
# Rename the file
new_filename = get_website_title(content)
if not new_filename:
new_filename = remove_head(filename)
new_filename = get_windows_filename(new_filename)
new_filename = os.path.splitext(new_filename)[0] + ".url"
log(f"New filename: {new_filename}")
new_filepath = get_unique_filepath(filepath, new_filename)
try:
os.rename(filepath, new_filepath)
log(f"Successfully renamed to: {os.path.basename(new_filepath)}")
except Exception as e:
log(f"Error renaming file: {e}")
log("") # Blank line for readability
return []
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment