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,425 of 90,437    |
|    street@shellcrash.com to All    |
|    AI Generated Usenet Client Update (1/3)    |
|    24 Dec 25 01:36:43    |
   
   AI Generated Usenet Client in Python   
      
   If you have any improvements to this or helpful ideas I would like to see them.   
      
   https://github.com/alt-magick/Newsgroup-Client-/   
      
   Newsgroup-Client   
      
   For Termux   
      
   This is a Python script that runs on Android in Termux. It is a full newsgroup   
   client for Usenet.   
      
   Menu Functions   
      
   Enter = Read - Opens current article   
      
   Space = Next - Moves to next article   
      
   Backspace = Previous - Moves to previous article   
      
   L = Reload - Loads new posts   
      
   J = Jump - Skip to an article   
      
   G = Group - Changes newsgroup   
      
   B = Batch - List articles   
      
   F = Author - Finds posts by author   
      
   S = Subject - Finds posts by subject   
      
   M = Body - Searches through message bodies   
      
   R = Replies - Reads replies to articles   
      
   N = New post - Posts to newsgroup   
      
   Y = Reply - Replies to current article   
      
   P = Page - Sets the length of each page   
      
   C = Reconnect - Reconnects to server   
      
   Q = Quit - Closes the newsgroup client   
      
   Hint: Pinch the terminal window to change the font size on Termux.   
      
   Full Python Code:   
      
   #!/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 uuid   
      
   # ================= USER CONFIG =================   
   NNTP_SERVER = "usnews.blocknews.net"   
   NNTP_PORT = 563   
   USERNAME = ""   
   PASSWORD = ""   
   PAGE_LINES = 25   
   MAX_ARTICLES_LIST = 200   
   START_GROUP = "alt.magick"   
   SHOW_REPLY_COUNT_MAIN = True   
   # ==============================================   
      
   RE_REPLY = re.compile(r"^(re|fwd):", re.IGNORECASE)   
   CLEAN_RE = re.compile(r"[ ---]")   
      
   # ---------- STATUS ----------   
   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):   
    print()   
    sys.stdout.write(text)   
    sys.stdout.flush()   
    return sys.stdin.readline().strip()   
      
   # ---------- PAGER ----------   
   def paged_print(lines):   
    i = 0   
    while i < len(lines):   
    for line in lines[i:i + PAGE_LINES]:   
    print(line)   
    i += PAGE_LINES   
    if i >= len(lines):   
    break   
    print("   
   --- ENTER = next page | SPACE = skip ---   
   ")   
    if get_key() == " ":   
    break   
    print()   
      
   # ---------- 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   
      
   # ---------- HEADER SANITIZER ----------   
   def sanitize_header(s):   
    return "".join(c if 32 <= ord(c) <= 126 else " " for c in s).strip()   
      
   # ---------- DISPLAY ----------   
   def show_article(nntp, num, group=None):   
    try:   
    _, body = nntp.body(str(num))   
    print()   
    if group:   
    print(f"Group: {group}   
   ")   
    paged_print([decode_body_line(l) for l in body.lines])   
    print()   
    except Exception as e:   
    set_status(f"Fetch failed: {e}")   
      
   # ---------- GROUP LOAD ----------   
   def reload_group(nntp, group):   
    try:   
    _, _, first, last, _ = nntp.group(group)   
    first, last = int(first), int(last)   
    _, overviews = nntp.over((max(first, last - MAX_ARTICLES_LIST), last))   
    posts = []   
    rel = 1   
    for num, hdr in reversed(overviews):   
    subject = hdr.get("subject", "")   
    if RE_REPLY.match(subject):   
    continue   
    msgid = hdr.get("message-id", "")   
    replies = sum(1 for _, h in overviews if msgid in h.   
   et("references", "")) if SHOW_REPLY_COUNT_MAIN else 0   
    posts.append({   
    "rel_num": rel,   
    "num": int(num),   
    "from": CLEAN_RE.sub("", hdr.get("from", "?")),   
    "date": hdr.get("date", "?"),   
    "subject": CLEAN_RE.sub("", subject),   
    "replies": replies,   
    "msgid": msgid   
    })   
    rel += 1   
    return posts   
    except Exception as e:   
    set_status(f"Reload failed: {e}")   
    return []   
      
   # ---------- HEADER SEARCH ----------   
   def header_search(nntp, group, field, keyword, count):   
    _, _, first, last, _ = nntp.group(group)   
    start = max(int(first), int(last) - count + 1)   
    _, overviews = nntp.over((start, last))   
    results = []   
    rel = 1   
    total = len(overviews)   
    for idx, (num, hdr) in enumerate(reversed(overviews), 1):   
    sys.stdout.write(f"   
   Searching headers... ({idx}/{total})")   
    sys.stdout.flush()   
    value = CLEAN_RE.sub("", hdr.get(field, ""))   
    if keyword.lower() in value.lower():   
    msgid = hdr.get("message-id", "")   
    replies = sum(1 for _, h in overviews if msgid in h.   
   et("references", "")) if SHOW_REPLY_COUNT_MAIN else 0   
    results.append({   
    "rel_num": rel,   
    "num": int(num),   
    "from": CLEAN_RE.sub("", hdr.get("from", "?")),   
    "date": hdr.get("date", "?"),   
    "subject": CLEAN_RE.sub("", hdr.get("subject", "")),   
    "replies": replies,   
    "msgid": msgid   
    })   
    rel += 1   
    sys.stdout.write("   
   Search complete!    
   ")   
    return results   
      
   # ---------- BODY SEARCH ----------   
   def body_search(nntp, group, keyword, count):   
    _, _, first, last, _ = nntp.group(group)   
    start = max(int(first), int(last) - count + 1)   
    _, overviews = nntp.over((start, last))   
    matches = []   
    rel = 1   
    total = len(overviews)   
    for idx, (num, hdr) in enumerate(reversed(overviews), 1):   
    sys.stdout.write(f"   
   Searching bodies... ({idx}/{total})")   
    sys.stdout.flush()   
    try:   
    _, body = nntp.body(str(num))   
    text = "   
   ".join(decode_body_line(l) for l in body.lines)   
    if keyword.lower() in text.lower():   
    msgid = hdr.get("message-id", "")   
    replies = sum(1 for _, h in overviews if msgid in   
   h.get("references", "")) if SHOW_REPLY_COUNT_MAIN else 0   
    matches.append({   
    "rel_num": rel,   
    "num": int(num),   
    "from": CLEAN_RE.sub("", hdr.get("from", "?")),   
    "date": hdr.get("date", "?"),   
    "subject": CLEAN_RE.sub("", hdr.get("subject", "")),   
      
   [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