#!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.8" # dependencies = [ # "requests", # ] # /// """ A command-line tool to upload files or stdin content to a paste service. Examples: # Upload a file (syntax auto-detected) ./pasted my_file.py # Upload stdin content echo "Hello World" | ./pasted # Upload with notifications and raw link ./pasted --notify --raw my_file.py # Upload with custom syntax and expiration ./pasted --syntax python --expiration 3600 my_file.py # Upload as public paste (syntax still auto-detected) ./pasted --exposure public my_file.py # Run with uvx uvx /path/to/pasted my_file.py Note: Syntax is automatically detected based on file content when not explicitly specified. """ import argparse import enum import logging import re import subprocess import sys from pathlib import Path from typing import Dict, Optional import requests class Result(enum.IntEnum): """API response codes from the paste service.""" Success = enum.auto() InvalidData = enum.auto() InvalidSyntax = enum.auto() InvalidExpiration = enum.auto() InvalidExposure = enum.auto() InvalidToken = enum.auto() Throttled = enum.auto() LINK = "https://paste.diath.net" ENDPOINT = "https://paste.diath.net/api" RESULT_MESSAGES: Dict[Result, str] = { Result.Success: "Paste added successfully.", Result.InvalidData: "Invalid form data supplied.", Result.InvalidSyntax: "Submitted syntax is not supported.", Result.InvalidExpiration: "Invalid expiration range specified.", Result.InvalidExposure: "Invalid exposure value specified.", Result.InvalidToken: "Invalid API token provided.", Result.Throttled: "You have been temporarily throttled by the system.", } class PasteError(Exception): """Custom exception for paste-related errors.""" pass def copy_to_clipboard(text: str) -> None: """Copy text to clipboard using xclip.""" import os # Skip silently if no display environment if not os.environ.get("DISPLAY") and not os.environ.get("WAYLAND_DISPLAY"): return try: # Try the exact same command that works in terminal proc = subprocess.Popen( ["xclip", "-selection", "clipboard"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=os.environ.copy() ) # Send input and close stdin immediately proc.stdin.write(text) proc.stdin.close() # Wait for process to complete with timeout try: stdout, stderr = proc.communicate(timeout=3) except subprocess.TimeoutExpired: proc.kill() proc.communicate() logging.debug("xclip timed out and was killed") return if proc.returncode != 0: logging.debug(f"xclip failed: {stderr}") except (FileNotFoundError, OSError) as e: logging.debug(f"Clipboard operation failed: {e}") except Exception as e: logging.debug(f"Unexpected clipboard error: {e}") def send_notification(title: str, message: str, icon: str = "emblem-default") -> None: """Send a desktop notification using notify-send.""" try: subprocess.run( ["notify-send", "-i", icon, title, message], check=True, capture_output=True, timeout=1 ) except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired) as e: logging.warning(f"Failed to send notification: {e}") def read_file_content(filepath: Path) -> str: """Read content from a file.""" try: return filepath.read_text(encoding="utf-8") except FileNotFoundError: raise PasteError(f"File doesn't exist: {filepath}") except PermissionError: raise PasteError(f"Permission denied: {filepath}") except UnicodeDecodeError: raise PasteError(f"Unable to decode file as UTF-8: {filepath}") def read_stdin_content() -> str: """Read content from stdin.""" if sys.stdin.isatty(): raise PasteError("No input provided via stdin") return sys.stdin.read() def create_parser() -> argparse.ArgumentParser: """Create and configure the argument parser.""" parser = argparse.ArgumentParser( description=f"Upload a local file or stdin contents to {LINK}." ) parser.add_argument( "--notify", action="store_true", default=False, help="Display notification." ) parser.add_argument( "--expiration", type=int, default=0, help="Set paste expiration." ) parser.add_argument( "--exposure", default="private", choices=("public", "private"), help="Set paste exposure.", ) parser.add_argument( "--syntax", default="None", help="Set paste syntax. If not specified or set to 'None', will attempt to auto-detect based on content." ) parser.add_argument( "filepath", nargs="?", type=Path, help="The path to the file that you want to upload." ) group = parser.add_mutually_exclusive_group() group.add_argument( "--raw", action="store_true", default=False, help="Return a link to a raw version of the entry.", ) group.add_argument( "--simple", action="store_true", default=False, help="Return a link to a simple version of the entry.", ) return parser def get_api_token() -> str: """Get API token from environment variable.""" import os token = os.environ.get("PASTE_D_TOKEN") if not token: raise PasteError( "This script requires an API token in the PASTE_D_TOKEN environment variable." ) return token def detect_syntax(content: str) -> Optional[str]: """Detect the syntax/language of the given content.""" if not content or len(content.strip()) < 10: return None # Programming languages (high priority) programming_languages = { "Python": [ re.compile(r"def\s+\w+\s*\([^)]*\):"), re.compile(r"import\s+\w+"), re.compile(r"from\s+\w+\s+import"), re.compile(r"if\s+__name__\s*==\s*['\"]__main__['\"]:") ], "JavaScript": [ re.compile(r"\b(function|let|const|=>)\b"), re.compile(r"console\.log\s*\("), re.compile(r"require\s*\("), re.compile(r"import.*from") ], "TypeScript": [ re.compile(r":\s*(string|number|boolean|any|void)\b"), re.compile(r"interface\s+\w+"), re.compile(r"type\s+\w+\s*=") ], "Java": [ re.compile(r"public\s+class\s+\w+"), re.compile(r"public\s+static\s+void\s+main"), re.compile(r"System\.out\.print"), re.compile(r"package\s+[\w.]+;") ], "C++": [ re.compile(r"#include\s*<iostream>"), re.compile(r"using\s+namespace\s+std"), re.compile(r"std::"), re.compile(r"\b(class|template|new|delete)\b") ], "C": [ re.compile(r"#include\s*<stdio\.h>"), re.compile(r"int\s+main\s*\("), re.compile(r"printf\s*\("), re.compile(r"\b(malloc|free)\b") ], "Go": [ re.compile(r"package\s+main"), re.compile(r"func\s+main\s*\("), re.compile(r"fmt\.Print"), re.compile(r"import\s*\(") ], "Rust": [ re.compile(r"fn\s+main\s*\("), re.compile(r"fn\s+\w+"), re.compile(r"println!\s*\("), re.compile(r"let\s+mut\s+") ], "PHP": [ re.compile(r"<\?php"), re.compile(r"\$\w+"), re.compile(r"function\s+\w+"), re.compile(r"echo\s") ], "Bash": [ re.compile(r"^#!/"), re.compile(r"\$\w+"), re.compile(r"echo\s"), re.compile(r"\[\s*.*\s*\]") ], "Lua": [ re.compile(r"function\s+\w+"), re.compile(r"local\s+\w+"), re.compile(r"print\s*\("), re.compile(r"end\b") ], "SQL": [ re.compile(r"\bSELECT\b.*\bFROM\b", re.IGNORECASE), re.compile(r"\bINSERT\s+INTO\b", re.IGNORECASE), re.compile(r"\bCREATE\s+TABLE\b", re.IGNORECASE) ] } # Markup/config languages (lower priority, simple patterns only) markup_languages = { "HTML": [ re.compile(r"<!DOCTYPE\s+html>", re.IGNORECASE), re.compile(r"<!html", re.IGNORECASE) ], "XML": [ re.compile(r"<\?xml\s+version", re.IGNORECASE) ], "CSS": [ re.compile(r"[.#]\w+\s*\{[^}]*\}") ], "JSON": [ re.compile(r"^\s*\{\s*\"[^\"]*\"\s*:"), re.compile(r"^\s*\[\s*\{") ], "YAML": [ re.compile(r"---"), re.compile(r"^\w+:\s*$", re.MULTILINE) ] } # First, try programming languages (priority) for language, patterns in programming_languages.items(): matches = 0 for pattern in patterns: if pattern.search(content): matches += 1 if matches >= 2: return language # Then try markup languages (only with obvious markers) for language, patterns in markup_languages.items(): for pattern in patterns: if pattern.search(content): return language return None def prepare_request_data(args: argparse.Namespace, content: str) -> Dict[str, str]: """Prepare the request data for the API call.""" syntax = args.syntax # Auto-detect syntax if not explicitly provided or set to "None" if syntax == "None": detected_syntax = detect_syntax(content) if detected_syntax: syntax = detected_syntax print(f"Auto-detected syntax: {syntax}") return { "token": get_api_token(), "exposure": "0" if args.exposure == "public" else "1", "expiration": str(args.expiration), "syntax": syntax, "content": content, } def get_link_suffix(args: argparse.Namespace) -> str: """Get the appropriate link suffix based on arguments.""" if args.raw: return "/raw" elif args.simple: return "/simple" return "" def handle_successful_response(response_text: str, args: argparse.Namespace) -> None: """Handle a successful API response.""" try: parts = response_text.split(":") if len(parts) < 2: raise ValueError("Invalid response format") code = int(parts[0]) hash_value = parts[1] if code != Result.Success: error_msg = RESULT_MESSAGES.get(Result(code), "Unknown error") print(f"#{code}: {error_msg}") if args.notify: send_notification( "Paste", f"Invalid return code returned by the endpoint.\n{error_msg}", "emblem-unreadable" ) return # Success case link = f"{LINK}/{hash_value}{get_link_suffix(args)}" print(RESULT_MESSAGES[Result.Success]) print(f"Hash: {hash_value}") print(f"Link: {link}") copy_to_clipboard(link) if args.notify: send_notification("Paste", "Paste has been added successfully.") except (ValueError, IndexError) as e: raise PasteError(f"Invalid response format: {e}") def main() -> None: """Main entry point of the script.""" logging.basicConfig(level=logging.WARNING) parser = create_parser() args = parser.parse_args() try: # Get content from file or stdin if args.filepath: content = read_file_content(args.filepath) else: content = read_stdin_content() # Prepare and send request data = prepare_request_data(args, content) response = requests.post(ENDPOINT, data=data, timeout=30) if response.status_code == requests.codes.ok: handle_successful_response(response.text, args) else: print(f"Invalid status code returned by the endpoint: #{response.status_code}") if args.notify: send_notification( "Paste", "Invalid status code returned by the endpoint.", "emblem-unreadable" ) except PasteError as e: print(f"Error: {e}") if args.notify: send_notification("Paste", f"Error: {e}", "emblem-unreadable") sys.exit(1) except requests.RequestException as e: print(f"Network error: {e}") if args.notify: send_notification("Paste", f"Network error: {e}", "emblem-unreadable") sys.exit(1) except KeyboardInterrupt: print("\nOperation cancelled by user.") sys.exit(1) if __name__ == "__main__": main()