from flask import Flask, render_template, make_response from flask_paginate import Pagination, get_page_parameter, get_page_args from flask_caching import Cache from flask import request import pendulum import subprocess import base64 import os import textwrap import sys import re import json def rot47(s): x = '' for c in s: j = ord(c) if j >= 33 and j <= 126: x += chr(33+ ((j+14) % 94)) else: x += c return x base_path = "/messagebase" app = Flask(__name__, static_url_path=base_path + "/static") @app.template_filter("datefmt") def format_datetime(value): # dt = pendulum.from_timestamp(value, tz=pendulum.tz.local_timezone()) dt = pendulum.from_timestamp(value) return dt.to_datetime_string() # Check Configuring Flask-Caching section for more details # cache = Cache(app, config={"CACHE_TYPE": "filesystem", "CACHE_DIR": "cache"}) cache = Cache( app, config={"CACHE_TYPE": "redis", "CACHE_REDIS_HOST": "redis"} ) # cache = Cache(app, config={"CACHE_TYPE": "redis", "CACHE_REDIS_HOST": "olympus"}) # import jammin import sqlite3 # Should this be in the actual calls? # (So I don't keep a connection open all the time?) dbconnect = sqlite3.connect("db/message.sqlite3") dbc = dbconnect.cursor() bases = { "FSXNET-General": "fsx_gen", "FSXNET-Ads": "fsx_ads", "FSXNET-BBS": "fsx_bbs", "FSXNET-BOT": "fsx_bot", "FSXNET-Encryption": "fsx_cry", "FSXNET-Network": "fsx_net", "FSXNET-Ham Radio": "fsx_ham", "FSXNET-Talisman - Magicka": "fsx_mag", "FSXNET-Mystic": "fsx_mys", "FSXNET-Enigma": "fsx_eng", "FSXNET-Gaming": "fsx_gaming", "FSXNET-Space & Astronomy": "fsx_space", "FSXNET-Sports": "fsx_sports", "FSXNET-Retro Computing": "fsx_retro", "FSXNET-Transport": "fsx_transport", "FSXNET-Video": "fsx_video", "FSXNET-Music": "fsx_music", "FSXNET-Do It Yourself": "fsx_diy", "FSXNET-Food": "fsx_food", "FSXNET-Gardening": "fsx_gardening", "FSXNET-Arts": "fsx_arts", "FSXNET-Data": "fsx_dat", # "HappyNet-General": "msgs/hpy_gen", } def bbs_get_messages(area): global dbc messages = [] for row in dbc.execute( # "SELECT message_id, to_user_name, from_user_name, subject, modified_timestamp from message WHERE area_tag=?", "SELECT message_id, to_user_name, from_user_name, subject, modified_timestamp from message WHERE area_tag=? ORDER BY message_id;", (area,), ): stamp = pendulum.parse(row[4]).timestamp() messages.append( { "MsgNum": row[0], "number": row[0], "to": row[1], "from": row[2], "subject": row[3], "written": stamp, # // written # // received # // processed } ) return messages MATCH1 = re.compile(">>> BEGIN(.*)>>> END", re.DOTALL) def bbs_message(area, msgno): global dbc messages = [] dbc.execute( "SELECT message_id, to_user_name, from_user_name, subject, modified_timestamp, message from message WHERE message_id=?", (msgno,), ) row = dbc.fetchone() if (not row): return stamp = pendulum.parse(row[4]).timestamp() data = { "MsgNum": row[0], "number": row[0], "to": row[1], "from": row[2], "subject": row[3], "written": stamp, "received": stamp, "processed": stamp, "text": row[5], # .decode("cp437"), "bytes": row[5].encode("cp437") # // written # // received # // processed } if (area == 'fsx_dat') and ('>>> BEGIN' in row[5]): body = row[5] + "\n" result = MATCH1.search(body) if result: data['rot47'] = rot47(result.group(1)).lstrip("\n").replace("\n", "
") return data # bases = {"FSX_BOT": "fsx_bot"} # @cache.memoize(timeout=5 * 60, key_prefix="messages") @cache.memoize(timeout=5 * 60) def get_messages(base): messages = bbs_get_messages(base) messages.reverse() return messages @cache.memoize(timeout=60) def get_message(base, msgno): # message = jammin.read_message(base, msgno) message = bbs_message(base, msgno) return message @app.errorhandler(404) def not_found(e): return render_template("404.html") @app.route(base_path + "/list") def list_bases(): return render_template( "list.html", bases=bases, base_path=base_path, title="Message Areas" ) # return 'Here would be a listing of message bases' @app.route(base_path + "/clear") def clear_cache(): cache.clear() return "Cache Cleared. Back to hitting refresh!" @app.route(base_path + "/messages/") def display_messages(area): if area not in bases: return render_template( "missing-area.html", base_path=base_path, title="Missing Area" ) # messages = jammin.get_messages(bases[area]) messages = get_messages(bases[area]) # messages.reverse() # cached.reverse() page = request.args.get(get_page_parameter(), type=int, default=1) # get_page_arg defaults to page 1, per_page of 10 PER_PAGE = 50 total = len(messages) pagination = Pagination( page=page, total=total, css_framework="foundation", record_name="messages", per_page=PER_PAGE, ) page, per_page, offset = get_page_args() start = (page - 1) * PER_PAGE end = start + PER_PAGE # messages = messages[(page-1) * PER_PAGE:offset+PER_PAGE] messages = messages[start:end] return render_template( "messages.html", messages=messages, area=area, pagination=pagination, base_path=base_path, title="Messages for " + bases[area], ) @cache.memoize(timeout=60) def ansi_to_png(raw_ansi_bytes, idx): pid = os.getppid() ansifile = "{0}-{1}.ans".format(idx, pid) pngfile = "{0}-{1}.png".format(idx, pid) with open(ansifile, "wb") as fp: fp.write(raw_ansi_bytes) subprocess.run(["./ansilove", "-d", "-o", pngfile, ansifile]) with open(pngfile, "rb") as fp: png = fp.read() os.unlink(ansifile) os.unlink(pngfile) return png def ansi_to_png64(raw_ansi_bytes, idx): png = ansi_to_png(raw_ansi_bytes, idx) return base64.b64encode(png).decode("utf-8") @app.route(base_path + "/image//.png") def display_ansi(area, msgno): if area not in bases: return "RATS", 404 message = get_message(bases[area], msgno) if not message: return "RATS", 404 if not "text" in message: return "RATS", 404 # png = ansi_to_png(message["bytes"].replace(b"\r", b"\n"), msgno) png = ansi_to_png(message["bytes"], msgno) # png = ansi_to_png(message["bytes"].replace("\r", "\n"), msgno) response = make_response(png) response.headers.set("Content-Type", "image/png") return response # My Image/") def display_message(area, msgno): if area not in bases: return render_template( "missing-area.html", base_path=base_path, title="Missing Area" ) # message = jammin.read_message(bases[area], msgno) message = get_message(bases[area], msgno) if not message: return render_template( "missing-message.html", base_path=base_path, area=area, title="Missing Message", ) messages = get_messages(bases[area]) # prevmsg and nextmsg are completely different now. prevmsg = None nextmsg = None total = len(messages) for idx, msg in enumerate(messages): if msg["MsgNum"] == msgno: # Ok, found what we're looking for if idx > 0: prevmsg = messages[idx - 1]["MsgNum"] if idx + 1 < total: nextmsg = messages[idx + 1]["MsgNum"] # prevmsg = None # nextmsg = None # if msgno > 1: # prevmsg = msgno - 1 # if msgno < total: # nextmsg = msgno + 1 if "text" in message: if "\x1b" in message["text"]: # Ok, the message contains ANSI CODES -- Convert message["png"] = True # message["png"] = ansi_to_png64( # message["bytes"].replace(b"\r", b"\n"), msgno # ) else: text = message["text"].replace("\r", "\n") # Ok, latest changes aren't doing word-wrap for us, so do it here. text = "\n".join( [ textwrap.fill(txt, width=78, replace_whitespace=False) for txt in text.splitlines() ] ) message["text"] = text # message["text"].replace("\r", "\n") #
\n") return render_template( "message.html", message=message, area=area, msgnumber=msgno, prevmsg=prevmsg, nextmsg=nextmsg, base_path=base_path, title="Message {0}".format(msgno), ) # LAST CALLERS PROCESSING def time_duration(time_delta): if (time_delta.in_seconds() < 60): return "{0} seconds ago".format(time_delta.in_seconds()) if (time_delta.in_minutes() < 60): return "{0} minutes ago".format(time_delta.in_minutes()) if (time_delta.in_hours() < 24): return "{0} hours ago".format(time_delta.in_hours()) return "{0} days ago".format(time_delta.in_days()) # in_months, in_years ... @cache.memoize(timeout=60) def last_bbs_callers(): dbsystem = sqlite3.connect("db/system.sqlite3") dbsys = dbsystem.cursor() dbuser = sqlite3.connect("db/user.sqlite3") dbusr = dbuser.cursor() # step 1: get list of last 25 callers users = [] lookup = set() now = pendulum.now() for row in dbsys.execute('select id,timestamp,log_value from system_event_log where log_name="user_login_history" order by id desc limit 25;'): # Ok! # row[0], row[1], row[2] jdata = json.loads(row[2]) called_when = pendulum.parse(row[1]) long_ago = time_duration(now - called_when) caller = { 'logid': row[0], 'timestamp': row[1], 'userId': jdata['userId'], 'ago': long_ago } lookup.add(jdata['userId']) users.append(caller) # Ok, we have a list of userIds to look up. # just look all 10 of them up. :P for row in dbusr.execute('select U.id,U.user_name, (SELECT prop_value FROM user_property AS UP WHERE U.id=user_id AND prop_name="location") as location from user as U;'): (userid, username, location) = row if userid in lookup: # Ok, we have something! for u in users: if u['userId'] == userid: u['username'] = username u['location'] = location return users; @app.route("/lastcallers") def display_lastcallers(): users=last_bbs_callers() return render_template( "lastcallers.html", users=users, title="Last Callers" )