Forums before death by AOL, social media and spammers... "We can't have nice things"
|    alt.magick    |    Meh.. another magic/spellcasting forum    |    90,437 messages    |
[   << oldest   |   < older   |   list   |   newer >   |   newest >>   ]
|    Message 90,419 of 90,437    |
|    street@shellcrash.com to All    |
|    AI Generated Usenet Client in Python (1/    |
|    19 Dec 25 04:17:16    |
   
   AI Generated Usenet Client in Python   
      
   https://github.com/alt-magick/Newsgroup-Client-/   
      
   #!/usr/bin/env python3   
   import nntplib   
   import sys   
   import termios   
   import tty   
   import re   
   import quopri   
   import base64   
   from email.message import EmailMessage   
   import os   
   import tempfile   
   import subprocess   
      
   # ================= USER CONFIG =================   
   NNTP_SERVER = "usnews.blocknews.net"   
   NNTP_PORT = 563   
   USERNAME = ""   
   PASSWORD = ""   
   PAGE_LINES = 12   
   MAX_ARTICLES_LIST = 200   
   MAX_REPLY_SCAN = 300   
   START_GROUP = "alt.test"   
   SHOW_REPLY_COUNT = False   
   SHOW_REPLY_COUNT_MAIN = True   
   # ==============================================   
      
   RE_REPLY = re.compile(r"^(re|fwd):", re.IGNORECASE)   
   CLEAN_RE = re.compile(r"[ ---]")   
      
   # ---------- STATUS LINE ----------   
   STATUS_LINE = ""   
      
   def set_status(msg):   
    global STATUS_LINE   
    STATUS_LINE = msg   
      
   def show_status():   
    global STATUS_LINE   
    if STATUS_LINE:   
    print(f"   
   [{STATUS_LINE}]")   
    STATUS_LINE = ""   
      
   # ---------- RAW KEY INPUT ----------   
   def get_key():   
    fd = sys.stdin.fileno()   
    old = termios.tcgetattr(fd)   
    try:   
    tty.setraw(fd)   
    return sys.stdin.read(1)   
    finally:   
    termios.tcsetattr(fd, termios.TCSADRAIN, old)   
      
   def prompt(text):   
    sys.stdout.write(text)   
    sys.stdout.flush()   
    return sys.stdin.readline().strip()   
      
   # ---------- HARD-CODED PAGER ----------   
   def paged_print(lines):   
    i = 0   
    total = len(lines)   
    while i < total:   
    end = min(i + PAGE_LINES, total)   
    for line in lines[i:end]:   
    print(line)   
    i = end   
    if i >= total:   
    break   
    print("   
   --- ENTER = next page | SPACE = skip ---")   
    if get_key() == " ":   
    break   
      
   # ---------- BODY DECODER ----------   
   def decode_body_line(line_bytes):   
    s = line_bytes.decode("utf-8", errors="replace")   
    s = CLEAN_RE.sub("", s)   
    if "=" in s:   
    try:   
    s = quopri.decodestring(s).decode("utf-8", errors="replace")   
    except Exception:   
    pass   
    if re.fullmatch(r"[A-Za-z0-9+/=\s]+", s) and len(s.strip()) > 20:   
    try:   
    s = base64.b64decode(s, validate=True).decode("utf-8",   
   errors="replace")   
    except Exception:   
    pass   
    return s   
      
   # ---------- POST BODY EDITOR ----------   
   def edit_body(initial=""):   
    editor = os.environ.get("EDITOR", "nano")   
    fd, path = tempfile.mkstemp(suffix=".txt")   
    try:   
    with os.fdopen(fd, "w") as f:   
    f.write(initial)   
    subprocess.call([editor, path])   
    with open(path, "r") as f:   
    return f.read()   
    finally:   
    os.unlink(path)   
      
   # ---------- POST BODY SOURCE ----------   
   def get_post_body():   
    print("   
   Post body source:")   
    print(" E = Edit in editor")   
    print(" F = Load from external text file")   
    print(" T = Type directly in terminal")   
    choice = get_key().lower()   
      
    if choice == "f":   
    path = prompt("   
   Enter path to text file: ")   
    try:   
    with open(path, "r", encoding="utf-8", errors="replace") as f:   
    body = f.read()   
    if not body.strip():   
    set_status("Post aborted (file is empty)")   
    return None   
    return body   
    except Exception as e:   
    set_status(f"Failed to read file: {e}")   
    return None   
      
    elif choice == "t":   
    print("   
   Type your post below. End with a period on a line by itself.")   
    lines = []   
    while True:   
    line = input()   
    if line.strip() == ".":   
    break # stop input when user enters a single period on a line   
    lines.append(line)   
    body = "   
   ".join(lines)   
    if not body.strip():   
    set_status("Post aborted (empty input)")   
    return None   
    return body   
      
    # default: editor   
    body = edit_body()   
    if not body.strip():   
    set_status("Post aborted (empty body)")   
    return None   
    return body   
      
   # ---------- POSTING ----------   
   def post_article(nntp, group, subject=None, references=None):   
    name = prompt("Enter your display name: ")   
    email = prompt("Enter your email: ")   
    if not subject:   
    subject = prompt("Enter subject: ")   
      
    body = get_post_body()   
    if not body:   
    return False   
      
    msg = EmailMessage()   
    msg["From"] = f"{name} <{email}>"   
    msg["Newsgroups"] = group   
    msg["Subject"] = subject   
    if references:   
    msg["References"] = references   
    msg.set_content(body)   
      
    try:   
    nntp.post(msg.as_bytes())   
    set_status("Article posted successfully")   
    return True   
    except Exception as e:   
    set_status(f"Post failed: {e}")   
    return False   
      
   # ---------- REPLY POSTING ----------   
   def post_reply(nntp, group, article_num):   
    try:   
    _, hinfo = nntp.head(str(article_num))   
    headers = {}   
    for raw in hinfo.lines:   
    line = decode_body_line(raw)   
    if ":" in line:   
    k, v = line.split(":", 1)   
    headers[k.lower()] = v.strip()   
      
    subject = headers.get("subject", "(no subject)")   
    if not RE_REPLY.match(subject):   
    subject = "Re: " + subject   
      
    refs = []   
    if "references" in headers:   
    refs.append(headers["references"])   
    if "message-id" in headers:   
    refs.append(headers["message-id"])   
      
    return post_article(nntp, group, subject, " ".join(refs))   
    except Exception as e:   
    set_status(f"Reply failed: {e}")   
    return False   
      
   # ---------- ARTICLE DISPLAY ----------   
   def show_article(nntp, num, group=None, allow_reply=False):   
    try:   
    _, hinfo = nntp.head(str(num))   
    headers = {}   
    for raw in hinfo.lines:   
    line = decode_body_line(raw)   
    if ":" in line:   
    k, v = line.split(":", 1)   
    headers[k.lower()] = v.strip()   
      
    _, body = nntp.body(str(num))   
    lines = [decode_body_line(l) for l in body.lines]   
      
    paged_print([   
    f"From: {headers.get('from','?')}",   
    f"Date: {headers.get('date','?')}",   
    f"Subject: {headers.get('subject','(no subject)')}",   
    ""   
    ] + lines)   
      
    if allow_reply and group:   
    print("   
   P=reply (any other key to continue)")   
    if get_key().lower() == "p":   
    post_reply(nntp, group, num)   
      
    except Exception as e:   
    set_status(f"Fetch failed: {e}")   
      
   # ---------- REPLY SCANNING ----------   
   def scan_replies_xover(nntp, msgid, first, last):   
    replies = []   
    start = max(first, last - MAX_REPLY_SCAN)   
    try:   
    _, overviews = nntp.over((start, last))   
    except:   
    return replies   
      
    for num, hdr in overviews:   
    if msgid in hdr.get("references", ""):   
    replies.append(int(num))   
    return replies   
      
      
   [continued in next message]   
      
   --- SoupGate-Win32 v1.05   
    * Origin: you cannot sedate... all the things you hate (1:229/2)   
|
[   << oldest   |   < older   |   list   |   newer >   |   newest >>   ]
(c) 1994, bbs@darkrealms.ca