#!/usr/bin/env python3 """ Parlobyg Email Client (info@parlobyg.dk) Usage: python3 email_client.py inbox --limit 10 python3 email_client.py unread python3 email_client.py read --id 123 python3 email_client.py search --query "from:example@mail.dk" python3 email_client.py folders python3 email_client.py send --to "to@example.dk" --subject "Subject" --body "Body" """ import argparse import imaplib import smtplib import ssl import email from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.header import decode_header from datetime import datetime import json import sys # Configuration IMAP_HOST = "imap.one.com" IMAP_PORT = 993 SMTP_HOST = "send.one.com" SMTP_PORT = 465 EMAIL_USER = "info@parlobyg.dk" EMAIL_PASS = "Q223Ln57TyyC4" def decode_mime_header(header): """Decode MIME encoded header.""" if header is None: return "" decoded_parts = decode_header(header) result = [] for part, encoding in decoded_parts: if isinstance(part, bytes): result.append(part.decode(encoding or 'utf-8', errors='replace')) else: result.append(part) return ' '.join(result) def get_imap_connection(): """Create IMAP connection.""" context = ssl.create_default_context() mail = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT, ssl_context=context) mail.login(EMAIL_USER, EMAIL_PASS) return mail def list_inbox(limit=10): """List recent inbox messages.""" mail = get_imap_connection() mail.select("INBOX") status, messages = mail.search(None, "ALL") if status != "OK": return {"error": "Failed to search inbox"} msg_ids = messages[0].split() msg_ids = msg_ids[-limit:] if len(msg_ids) > limit else msg_ids msg_ids.reverse() # Most recent first results = [] for msg_id in msg_ids: status, msg_data = mail.fetch(msg_id, "(RFC822.HEADER)") if status == "OK": msg = email.message_from_bytes(msg_data[0][1]) results.append({ "id": msg_id.decode(), "from": decode_mime_header(msg["From"]), "subject": decode_mime_header(msg["Subject"]), "date": msg["Date"] }) mail.logout() return {"count": len(results), "messages": results} def list_unread(): """List unread messages.""" mail = get_imap_connection() mail.select("INBOX") status, messages = mail.search(None, "UNSEEN") if status != "OK": return {"error": "Failed to search unread"} msg_ids = messages[0].split() if messages[0] else [] results = [] for msg_id in msg_ids: status, msg_data = mail.fetch(msg_id, "(RFC822.HEADER)") if status == "OK": msg = email.message_from_bytes(msg_data[0][1]) results.append({ "id": msg_id.decode(), "from": decode_mime_header(msg["From"]), "subject": decode_mime_header(msg["Subject"]), "date": msg["Date"] }) mail.logout() return {"unread_count": len(results), "messages": results} def read_email(msg_id): """Read a specific email by ID.""" mail = get_imap_connection() mail.select("INBOX") status, msg_data = mail.fetch(str(msg_id).encode(), "(RFC822)") if status != "OK": return {"error": f"Failed to fetch email {msg_id}"} msg = email.message_from_bytes(msg_data[0][1]) # Extract body body = "" if msg.is_multipart(): for part in msg.walk(): if part.get_content_type() == "text/plain": payload = part.get_payload(decode=True) if payload: body = payload.decode(part.get_content_charset() or 'utf-8', errors='replace') break else: payload = msg.get_payload(decode=True) if payload: body = payload.decode(msg.get_content_charset() or 'utf-8', errors='replace') mail.logout() return { "id": msg_id, "from": decode_mime_header(msg["From"]), "to": decode_mime_header(msg["To"]), "subject": decode_mime_header(msg["Subject"]), "date": msg["Date"], "body": body[:5000] # Truncate long bodies } def search_emails(query, limit=20): """Search emails with IMAP search criteria.""" mail = get_imap_connection() mail.select("INBOX") # Parse simple query format search_criteria = "ALL" if query.startswith("from:"): search_criteria = f'FROM "{query[5:]}"' elif query.startswith("subject:"): search_criteria = f'SUBJECT "{query[8:]}"' elif query.startswith("to:"): search_criteria = f'TO "{query[3:]}"' else: search_criteria = f'BODY "{query}"' status, messages = mail.search(None, search_criteria) if status != "OK": return {"error": "Search failed"} msg_ids = messages[0].split() if messages[0] else [] msg_ids = msg_ids[-limit:] if len(msg_ids) > limit else msg_ids msg_ids.reverse() results = [] for msg_id in msg_ids: status, msg_data = mail.fetch(msg_id, "(RFC822.HEADER)") if status == "OK": msg = email.message_from_bytes(msg_data[0][1]) results.append({ "id": msg_id.decode(), "from": decode_mime_header(msg["From"]), "subject": decode_mime_header(msg["Subject"]), "date": msg["Date"] }) mail.logout() return {"query": query, "count": len(results), "messages": results} def list_folders(): """List all folders.""" mail = get_imap_connection() status, folders = mail.list() if status != "OK": return {"error": "Failed to list folders"} folder_names = [] for folder in folders: # Parse folder response parts = folder.decode().split(' "/" ') if len(parts) >= 2: folder_names.append(parts[1].strip('"')) mail.logout() return {"count": len(folder_names), "folders": folder_names[:100]} # Limit output def send_email(to, subject, body, cc=None): """Send an email.""" msg = MIMEMultipart() msg["From"] = EMAIL_USER msg["To"] = to msg["Subject"] = subject if cc: msg["Cc"] = cc msg.attach(MIMEText(body, "plain", "utf-8")) try: context = ssl.create_default_context() with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, context=context) as server: server.login(EMAIL_USER, EMAIL_PASS) recipients = [to] + ([cc] if cc else []) server.sendmail(EMAIL_USER, recipients, msg.as_string()) return {"status": "sent", "to": to, "subject": subject} except Exception as e: return {"error": str(e)} def main(): parser = argparse.ArgumentParser(description="Parlobyg Email Client") subparsers = parser.add_subparsers(dest="command", help="Available commands") # Inbox inbox_parser = subparsers.add_parser("inbox", help="List recent inbox messages") inbox_parser.add_argument("--limit", type=int, default=10) # Unread subparsers.add_parser("unread", help="List unread messages") # Read read_parser = subparsers.add_parser("read", help="Read a specific email") read_parser.add_argument("--id", required=True, help="Email ID") # Search search_parser = subparsers.add_parser("search", help="Search emails") search_parser.add_argument("--query", required=True, help="Search query (from:, subject:, to:, or text)") search_parser.add_argument("--limit", type=int, default=20) # Folders subparsers.add_parser("folders", help="List folders") # Send send_parser = subparsers.add_parser("send", help="Send an email") send_parser.add_argument("--to", required=True, help="Recipient") send_parser.add_argument("--subject", required=True, help="Subject") send_parser.add_argument("--body", required=True, help="Body text") send_parser.add_argument("--cc", help="CC recipient") args = parser.parse_args() if args.command == "inbox": result = list_inbox(args.limit) elif args.command == "unread": result = list_unread() elif args.command == "read": result = read_email(args.id) elif args.command == "search": result = search_emails(args.query, args.limit) elif args.command == "folders": result = list_folders() elif args.command == "send": result = send_email(args.to, args.subject, args.body, args.cc) else: parser.print_help() sys.exit(1) print(json.dumps(result, indent=2, ensure_ascii=False)) if __name__ == "__main__": main()