Browse Source

Initial commit.

root 5 years ago
commit
9b32188fef
7 changed files with 702 additions and 0 deletions
  1. 6 0
      .gitignore
  2. 150 0
      bzbz.ini
  3. 6 0
      local_report.sh
  4. 150 0
      prison.ini
  5. 6 0
      prison_report.sh
  6. 11 0
      req.txt
  7. 373 0
      talker.py

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+bin/
+lib/
+lib64
+pyvenv.cfg
+share/
+

+ 150 - 0
bzbz.ini

@@ -0,0 +1,150 @@
+; MUTIL configuration file
+; -------------------------------------------------------------------------
+;
+; MUTIL is an automated maintainance and utility program driven by
+; configuration files used to perform various tasks upon execution.
+;
+; The syntax to execute MUTIL is: MUTIL <configuration file>.  If no
+; configuration file is supplied, then the program will look for mutil.ini
+; in the same directory.  A second parameter -NOSCREEN can be used to turn
+; off screen updates but the configuration file parameter MUST be supplied
+; ex: mutil mutil.ini -NOSCREEN
+;
+; Many different configuration files can exist to perform any series of
+; tasks supported by MUTIL, or a single configuration can be maintained that
+; does everything.  The minimum requirement is the General header with a
+; task enabled, and then the specific header for that task that defines its
+; options.
+;
+; Current abilities (enabled/disabled in the General header below):
+;
+;    - Export Binkley-style FLO echomail/netmail
+;    - Import Binkley-style FLO echomail/netmail
+;    - Import Message Bases (by datafile analysis)
+;    - Import FIDONET.NA into Message bases
+;    - Import FILEBONE.NA into File bases
+;    - Mass upload files to all file bases (with FILE_ID.DIZ import and the
+;      ability to exclude files by filemask)
+;    - Generate Top 1 up to 99 Callers, Posters, Downloaders, Uploaders, PCR
+;      Completely configurable output by using template files
+;    - Import FILES.BBS into file bases
+;    - Generate all files listing
+;    - Purge Message bases (by age and max messages)
+;    - Pack and renumber message bases
+;    - Post text files to message bases
+;    - Merge nodelists into Mystic format
+;    - Toss TIC+files into BBS and to subscribed downlinks
+;    - Pack and check integrity of file base listings
+;    - Sort file base listings
+;    - Perform message base echomail reply linking
+;    - Purge user database marking inactive users for deletion
+;    - Pack user database and remove private messages of deleted users
+;
+; The concept here is that you can create your own custom command lines to
+; complete any number of tasks.  For example, you could create msgmaint.ini
+; which executes message base purge, packing, and reply linking.  Then you
+; can simply execute "mutil msgmaint" to kick it off.
+; ==========================================================================
+; ==========================================================================
+; ==========================================================================
+
+[General]
+
+	; list of functions to perform on startup
+
+	PostTextFiles      = true
+
+	; Set this value if you want to run mutil from a directory other than
+        ; the root Mystic directory or the mysticbbs environment variable:
+
+	; mystic_directory=c:\mystic\mystic.dat
+
+	; If no directory is specified in the logfile name, mUtil will attempt
+        ; to use the configured LOGS directory from in Mystic's configuration.
+        ; Comment out to disable logging completely.
+
+	logfile=spaceBOT.log
+
+	; If set to TRUE (*HIGHLY* recommended for MUTIL) then it will write the
+	; log file in increments of 8KB at a time.  If set to FALSE it will write
+	; each individual line as it is logged.  This will significantly reduce
+	; MUTIL performance if set to FALSE.
+
+	logcache=true
+
+	; Level 1 = basic
+	; Level 2 = verbose
+	; Level 3 = debug
+
+	loglevel=3
+
+        ; logfile time stamp.  defaults to NNN DD HH:II:SS if not set here
+        ;logstamp = YYYYHHMMHHIISS
+
+	; Log roller type:
+	;   0 = Do not roll log files
+	;   1 = Roll by number of files/filesize
+	;   2 = Roll by number of days
+
+	logtype = 2
+
+        ; number of log files to keep (0 to disable log rolling)
+        maxlogfiles = 31
+
+        ; size of each log file in kilobytes
+        maxlogsize = 1500
+
+; ==========================================================================
+; ==========================================================================
+; ==========================================================================
+
+[PostTextFiles]
+
+	; Total number of text files to be posted.  For each file there needs to
+	; be a file definition as show below.
+
+	totalfiles = 2
+
+	; This defines one file which will be posted to the message base.  Each
+	; file should be prefixed with file# where # is a number from 1 to
+	; totalfiles.
+	;
+	; The delfile option (if true) will remove the filename after the message
+	; is posted.  The baseidx is the permanent index of the message base to
+	; post the message into (shown as Index at the top of the message base
+	; editor in Mystic's configuration.  Address is the echomail destination
+	; address
+	;
+	; file#_baseidx
+	; 3 is Local Test Messages
+	; 8 is fsxNet Bot Channel
+
+	file1_name    = venv/bzbz.bbs
+	file1_baseidx = 8
+	file1_from    = bugz_bot
+	file1_to      = All
+	file1_subj    = Space Report for BZ&BZ BBS
+	; file1_subj    = [ASCII] BZ&BZ BBS
+	file1_addr    = 0:0/0
+	file1_delfile = false
+
+	file2_name    = venv/bzbz.ans
+	file2_baseidx = 8
+	file2_from    = bugz_bot
+	file2_to      = All
+	file2_subj    = [ANSI] Space Report for BZ&BZ BBS
+	file2_addr    = 0:0/0
+	file2_delfile = false
+
+	; file2_name    = myfile.txt
+	; file2_baseidx = 2
+	; file2_from    = Sysop
+	; file2_to      = All
+	; file2_subj    = My subject
+	; file2_addr    = 0:0/0
+	; file2_delfile = false
+
+; ==========================================================================
+; ==========================================================================
+; ==========================================================================
+

+ 6 - 0
local_report.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+cd /home/mystic/mystic/bbs/venv
+
+./talker.py --report bzbz --name "BZ&BZ BBS Trade Wars" --host twgs
+

+ 150 - 0
prison.ini

@@ -0,0 +1,150 @@
+; MUTIL configuration file
+; -------------------------------------------------------------------------
+;
+; MUTIL is an automated maintainance and utility program driven by
+; configuration files used to perform various tasks upon execution.
+;
+; The syntax to execute MUTIL is: MUTIL <configuration file>.  If no
+; configuration file is supplied, then the program will look for mutil.ini
+; in the same directory.  A second parameter -NOSCREEN can be used to turn
+; off screen updates but the configuration file parameter MUST be supplied
+; ex: mutil mutil.ini -NOSCREEN
+;
+; Many different configuration files can exist to perform any series of
+; tasks supported by MUTIL, or a single configuration can be maintained that
+; does everything.  The minimum requirement is the General header with a
+; task enabled, and then the specific header for that task that defines its
+; options.
+;
+; Current abilities (enabled/disabled in the General header below):
+;
+;    - Export Binkley-style FLO echomail/netmail
+;    - Import Binkley-style FLO echomail/netmail
+;    - Import Message Bases (by datafile analysis)
+;    - Import FIDONET.NA into Message bases
+;    - Import FILEBONE.NA into File bases
+;    - Mass upload files to all file bases (with FILE_ID.DIZ import and the
+;      ability to exclude files by filemask)
+;    - Generate Top 1 up to 99 Callers, Posters, Downloaders, Uploaders, PCR
+;      Completely configurable output by using template files
+;    - Import FILES.BBS into file bases
+;    - Generate all files listing
+;    - Purge Message bases (by age and max messages)
+;    - Pack and renumber message bases
+;    - Post text files to message bases
+;    - Merge nodelists into Mystic format
+;    - Toss TIC+files into BBS and to subscribed downlinks
+;    - Pack and check integrity of file base listings
+;    - Sort file base listings
+;    - Perform message base echomail reply linking
+;    - Purge user database marking inactive users for deletion
+;    - Pack user database and remove private messages of deleted users
+;
+; The concept here is that you can create your own custom command lines to
+; complete any number of tasks.  For example, you could create msgmaint.ini
+; which executes message base purge, packing, and reply linking.  Then you
+; can simply execute "mutil msgmaint" to kick it off.
+; ==========================================================================
+; ==========================================================================
+; ==========================================================================
+
+[General]
+
+	; list of functions to perform on startup
+
+	PostTextFiles      = true
+
+	; Set this value if you want to run mutil from a directory other than
+        ; the root Mystic directory or the mysticbbs environment variable:
+
+	; mystic_directory=c:\mystic\mystic.dat
+
+	; If no directory is specified in the logfile name, mUtil will attempt
+        ; to use the configured LOGS directory from in Mystic's configuration.
+        ; Comment out to disable logging completely.
+
+	logfile=spaceBOT.log
+
+	; If set to TRUE (*HIGHLY* recommended for MUTIL) then it will write the
+	; log file in increments of 8KB at a time.  If set to FALSE it will write
+	; each individual line as it is logged.  This will significantly reduce
+	; MUTIL performance if set to FALSE.
+
+	logcache=true
+
+	; Level 1 = basic
+	; Level 2 = verbose
+	; Level 3 = debug
+
+	loglevel=3
+
+        ; logfile time stamp.  defaults to NNN DD HH:II:SS if not set here
+        ;logstamp = YYYYHHMMHHIISS
+
+	; Log roller type:
+	;   0 = Do not roll log files
+	;   1 = Roll by number of files/filesize
+	;   2 = Roll by number of days
+
+	logtype = 2
+
+        ; number of log files to keep (0 to disable log rolling)
+        maxlogfiles = 31
+
+        ; size of each log file in kilobytes
+        maxlogsize = 1500
+
+; ==========================================================================
+; ==========================================================================
+; ==========================================================================
+
+[PostTextFiles]
+
+	; Total number of text files to be posted.  For each file there needs to
+	; be a file definition as show below.
+
+	totalfiles = 2
+
+	; This defines one file which will be posted to the message base.  Each
+	; file should be prefixed with file# where # is a number from 1 to
+	; totalfiles.
+	;
+	; The delfile option (if true) will remove the filename after the message
+	; is posted.  The baseidx is the permanent index of the message base to
+	; post the message into (shown as Index at the top of the message base
+	; editor in Mystic's configuration.  Address is the echomail destination
+	; address
+	;
+	; file#_baseidx
+	; 3 is Local Test Messages
+	; 8 is fsxNet Bot Channel
+
+	file1_name    = venv/prison.bbs
+	file1_baseidx = 8
+	file1_from    = bugz_bot
+	file1_to      = All
+	file1_subj    = Space Report for Prison BBS
+	; file1_subj    = [ASCII] BZ&BZ BBS
+	file1_addr    = 0:0/0
+	file1_delfile = false
+
+	file2_name    = venv/prison.ans
+	file2_baseidx = 8
+	file2_from    = bugz_bot
+	file2_to      = All
+	file2_subj    = [ANSI] Space Report for Prison BBS
+	file2_addr    = 0:0/0
+	file2_delfile = false
+
+	; file2_name    = myfile.txt
+	; file2_baseidx = 2
+	; file2_from    = Sysop
+	; file2_to      = All
+	; file2_subj    = My subject
+	; file2_addr    = 0:0/0
+	; file2_delfile = false
+
+; ==========================================================================
+; ==========================================================================
+; ==========================================================================
+

+ 6 - 0
prison_report.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+cd /home/mystic/mystic/bbs/venv
+
+./talker.py --report prison --name "Prison Server at rdfig.net" --host rdfig.net --games AB --prompt "Description of Games"
+

+ 11 - 0
req.txt

@@ -0,0 +1,11 @@
+async-generator==1.10
+attrs==19.3.0
+colorama==0.4.3
+contextvars==2.4
+idna==2.9
+immutables==0.11
+outcome==1.0.1
+pkg-resources==0.0.0
+sniffio==1.1.0
+sortedcontainers==2.1.0
+trio==0.13.0

+ 373 - 0
talker.py

@@ -0,0 +1,373 @@
+#!/home/mystic/mystic/bbs/venv/bin/python
+
+import trio
+import sys
+from pprint import pprint
+import json
+from colorama import Fore, Back, Style
+import argparse
+import string
+
+def merge(color_string):
+    """ Given a string of colorama ANSI, merge them if you can. """
+    return color_string.replace("m\x1b[", ";")
+
+
+parser = argparse.ArgumentParser(description="TradeWars Score Bulletins Puller")
+parser.add_argument("--host", type=str, help="Hostname to contact", default="127.0.0.1")
+parser.add_argument("--port", type=int, help="Port number", default=2002)
+parser.add_argument("--username", type=str, help="RLogin Username", default="phil")
+parser.add_argument("--password", type=str, help="RLogin Password", default="phil")
+parser.add_argument("--games", type=str, help="TWGS Games to select")
+parser.add_argument("--name", type=str, help="Server Name for Report")
+parser.add_argument("--report", type=str, help="Space Reports base filename")
+parser.add_argument("--debug", action="store_true")
+parser.add_argument(
+    "--prompt", type=str, help="TWGS Custom Menu Prompt", default="Quit"
+)
+
+# Unfortunately, this can't figure out the custom prison TWGS menus.  :(
+# I would like this to allow you to select which reports it'll do.
+# So I could do the prison report with games A and B.
+#
+
+args = parser.parse_args()
+HOST = args.host
+# PORT=2003
+PORT = args.port
+
+state = 1
+options = {}
+pos = "A"
+scoreboard = {}
+settings = {}
+setup = {}
+
+if args.games is not None:
+    options = {x: x for x in args.games}
+    print("We will pull:", ",".join(options.keys()))
+
+# NOTE:  --games does not work unless you are also using prompt
+
+if args.debug:
+    print("ARGS:")
+    pprint(args)
+    print("=" * 40)
+
+import re
+
+# Cleans all ANSI
+cleaner = re.compile(r"\x1b\[[0-9;]*[A-Zmh]")
+
+
+def cleanANSI(line):
+    """ Remove all ANSI codes. """
+    global cleaner
+    return cleaner.sub("", line)
+
+def ANSIsplit(line, pos=70):
+    # Verify that we're not inside an ANSI sequence at this very moment.
+    save = "\x1b[s"
+    restore = "\x1b[u"
+
+    ansi = line.rindex("\x1b[", 0, pos-1)
+    start_ansi = ansi
+    ansi += 2
+    # to_check = string.digits + string.ascii_letters + ";"
+    to_check = string.digits + ";"
+
+    while line[ansi] in to_check:
+        ansi += 1
+    if line[ansi] in string.ascii_letters:
+        ansi += 1
+
+    if ansi > pos:
+        # Ok, pos is inside an ANSI escape code.  break at the start of the
+        # ANSI code.
+        ansi = start_ansi
+    else:
+        next_ansi = line.index("\x1b[", start_ansi + 1)
+        if pos < next_ansi:
+            ansi = pos
+        else:
+            ansi = next_ansi
+
+    # else:
+    #    next_ansi = line.index("\x1b[", start_ansi + 1)
+    #    if pos < next_ansi:
+    #        ansi = pos
+    #    else:
+    #        ansi = next_ansi
+
+    parts = (line[0:ansi] + save, restore + line[ansi:])
+    return parts
+
+def output(ansi, bbs, line):
+    cleaned = cleanANSI(line)
+    if len(line) > 60:
+        line = re.sub(
+            r"\s{5,}", lambda spaces: "\x1b[{0}C".format(len(spaces.group(0))), line
+        )
+    if len(line) > 65:
+        lines = ANSIsplit(line, 65)
+        print(lines[0], file=ansi)
+        print(lines[1], file=ansi)
+    else:
+        print(line, file=ansi)
+    print(cleaned, file=bbs)
+
+
+async def space_report():
+    global args, scoreboard, settings, setup
+
+    # Clean up the scoreboard
+    for k in scoreboard:
+        l = scoreboard[k][0]
+        # _, x, l = l.rpartition("\x1b[2J")
+        _, x, l = l.rpartition("\x1b[H")
+        scoreboard[k][0] = l
+
+    nl = "\r\n"
+    reset = Style.RESET_ALL
+    cyan = merge(Fore.CYAN + Style.BRIGHT)
+    white = merge(Fore.WHITE + Style.BRIGHT)
+    green = merge(Fore.GREEN + Style.NORMAL)
+
+    if args.report:
+        # Ok, there's a base filename for the report.
+        ansi = args.report + ".ans"
+        bbs = args.report + ".bbs"
+        aFP = open(ansi, "w")
+        bFP = open(bbs, "w")
+
+        output(aFP, bFP, "{0}Space Report for {1}{2}".format(cyan, white, args.name))
+        output(aFP, bFP, "")
+
+        for k in sorted(scoreboard):
+            name = options[k]
+            if name == k:
+                name = ""
+            output(
+                aFP, bFP, "{0}Game {1} {2}{3}{4}".format(cyan, k, white, name, reset)
+            )
+
+            # Ok, what settings would I want displayed?
+            # >> Age of game      : 65 days              Days since start    : 65 days
+            # >> Delete if idle   : 30 days
+            # >> Time per day     : Unlimited            Turns per day       : 10000
+            # >> Sectors in game  : 25000                Display StarDock    : Yes
+            line = "{0}{1:16} : {2}{3:20}{0}{4:16} : {2}{5}{6}".format(
+                green,
+                "Age of game",
+                cyan,
+                setup[k]["Age of game"],
+                "Days since start",
+                setup[k]["Days since start"],
+                reset,
+            )
+            output(aFP, bFP, line)
+
+            line = "{0}{1:16} : {2}{3:20}{0}{4:16} : {2}{5}{6}".format(
+                green,
+                "Time per day",
+                cyan,
+                setup[k]["Time per day"],
+                "Turns per day",
+                setup[k]["Turns per day"],
+                reset,
+            )
+            output(aFP, bFP, line)
+
+            line = "{0}{1:16} : {2}{3:20}{0}{4:16} : {2}{5}{6}".format(
+                green,
+                "Delete if idle",
+                cyan,
+                setup[k]["Delete if idle"],
+                "Sectors in game",
+                setup[k]["Sectors in game"],
+                reset,
+            )
+            output(aFP, bFP, line)
+
+            # line = "{0}{1:16} : {2}{3}".format(
+            #     green, "Delete if idle", cyan, setup[k]["Delete if idle"], reset
+            # )
+            # output(aFP, bFP, line)
+            output(aFP, bFP, "")
+
+            for line in scoreboard[k]:
+                output(aFP, bFP, line)
+
+        aFP.close()
+        bFP.close()
+
+    for k in scoreboard:
+        print(k, "\n".join(scoreboard[k]))
+
+    # pprint(scoreboard)
+    # for k in settings:
+    #     print(k, "\n".join(settings[k]))
+
+    if False:
+        for k in settings:
+            print(k)
+            for l in settings[k]:
+                # if "[Pause]" in l:
+                #    break
+                print(l)
+        pprint(settings)
+
+    # pprint(setup)
+    if args.report:
+        # Save all the configuration settings to json
+        filename = args.report + ".json"
+        print("Saving setup to:", filename)
+        with open(filename, "w") as fp:
+            json.dump(setup, fp)
+
+
+async def send(client, data):
+    global args
+    if args.debug:
+        print("<<", data)
+    await client.send_all(data.encode("latin-1"))
+
+
+async def readline(client_stream, line):
+    global state, pos, settings, args
+
+    if state == 1:
+        if "<" in line:
+            parts = {x[0]: x[2:].strip() for x in line.split("<") if x != ""}
+            options.update(parts)
+
+        # if "Quit" in line:
+        if args.prompt in line:
+            if args.debug:
+                print("Prompt seen!")
+            state += 1
+            if pos in options:
+                await send(client_stream, pos)
+            else:
+                await send(client_stream, "Q\r")
+    elif state == 3:
+        if pos not in scoreboard:
+            scoreboard[pos] = []
+        if (
+            not scoreboard[pos]
+            and (line == "")
+            or (("Enter your choice" in line) or ("Ranking Traders" in line))
+        ):
+            return
+        scoreboard[pos].append(line)
+
+    elif state == 5:
+        if pos not in settings:
+            settings[pos] = []
+            setup[pos] = {}
+        if not settings[pos] and (line == "" or "Enter your choice" in line):
+            return
+        settings[pos].append(line)
+        if ":" in line:
+            line = cleanANSI(line)
+            part2 = line[40:]
+            if ":" in part2:
+                # yes, this is a 2 parter
+                parts = line[0:39].strip().split(":")
+            else:
+                parts = line.strip().split(":")
+                part2 = ""
+
+            setup[pos][parts[0].strip()] = parts[1].strip()
+            if ":" in part2:
+                parts = part2.strip().split(":")
+                setup[pos][parts[0].strip()] = parts[1].strip()
+
+    elif state == 6:
+        # if "Quit" in line:
+        if args.prompt in line:
+            pos = chr(ord(pos) + 1)
+            if pos in options:
+                state = 2
+                await send(client_stream, pos)
+            else:
+                await send(client_stream, "q\r")
+
+    # elif state == 2:
+    #     if "[Pause]" in line:
+    #         await client_stream.send_all("\r".encode("latin-1"))
+
+    if args.debug:
+        print(">>", line)
+
+
+async def prompt(client_stream, line):
+    global state, pos
+
+    if state == 2:
+        if "[Pause]" in line:
+            # await client_stream.send_all("\r".encode("latin-1"))
+            await send(client_stream, "\r")
+        if "Enter your choice" in line:
+            state = 3
+            await send(client_stream, "H\r")
+    elif state == 3:
+        if "[Pause]" in line:
+            state = 4
+            # await client_stream.send_all("\r".encode("latin-1"))
+            await send(client_stream, "\r")
+            # pos = chr(ord(pos) + 1)
+    elif state == 4:
+        if "Enter your choice" in line:
+            state = 5
+            await send(client_stream, "S\r")
+    elif state == 5:
+        if "[Pause]" in line:
+            await send(client_stream, "\r")
+        if "Enter your choice" in line:
+            state = 6
+            await send(client_stream, "X\r")
+
+    if args.debug:
+        print(">>> {!r}".format(line))
+
+
+async def receiver(client_stream):
+    global args
+
+    if args.debug:
+        print("receiver: started")
+    # I need to send the rlogin connection information ...
+    rlogin = "\x00{0}\x00{1}\x00ansi-bbs\x009600\x00".format(
+        args.username, args.password
+    )
+    await client_stream.send_all(rlogin.encode("utf-8"))
+    buffer = ""
+    async for chunk in client_stream:
+        buffer += chunk.decode("latin-1", "ignore")
+        while "\n" in buffer:
+            part = buffer.partition("\n")
+            line = part[0].replace("\r", "")
+            buffer = part[2]
+            await readline(client_stream, line)
+
+        buffer = buffer.replace("\r", "")
+        if buffer != "":
+            await prompt(client_stream, buffer)
+
+    print("receiver: closed")
+    # pprint(options)
+
+    await space_report()
+
+
+async def start():
+    print("connecting...")
+    client_stream = await trio.open_tcp_stream(HOST, PORT)
+    async with client_stream:
+        async with trio.open_nursery() as nursery:
+            nursery.start_soon(receiver, client_stream)
+
+
+trio.run(start)
+