messages.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. from flask import Flask, render_template, make_response
  2. from flask_paginate import Pagination, get_page_parameter, get_page_args
  3. from flask_caching import Cache
  4. from flask import request
  5. import pendulum
  6. import subprocess
  7. import base64
  8. import os
  9. import textwrap
  10. import sys
  11. import re
  12. def rot47(s):
  13. x = ''
  14. for c in s:
  15. j = ord(c)
  16. if j >= 33 and j <= 126:
  17. x += chr(33+ ((j+14) % 94))
  18. else:
  19. x += c
  20. return x
  21. base_path = "/messagebase"
  22. app = Flask(__name__, static_url_path=base_path + "/static")
  23. @app.template_filter("datefmt")
  24. def format_datetime(value):
  25. # dt = pendulum.from_timestamp(value, tz=pendulum.tz.local_timezone())
  26. dt = pendulum.from_timestamp(value)
  27. return dt.to_datetime_string()
  28. # Check Configuring Flask-Caching section for more details
  29. # cache = Cache(app, config={"CACHE_TYPE": "filesystem", "CACHE_DIR": "cache"})
  30. cache = Cache(
  31. app, config={"CACHE_TYPE": "redis", "CACHE_REDIS_HOST": "redis"}
  32. )
  33. # cache = Cache(app, config={"CACHE_TYPE": "redis", "CACHE_REDIS_HOST": "olympus"})
  34. # import jammin
  35. import sqlite3
  36. dbconnect = sqlite3.connect("db/message.sqlite3")
  37. dbc = dbconnect.cursor()
  38. bases = {
  39. "FSXNET-General": "fsx_gen",
  40. "FSXNET-Ads": "fsx_ads",
  41. "FSXNET-BBS": "fsx_bbs",
  42. "FSXNET-BOT": "fsx_bot",
  43. # "FSXNET-Encryption": "msgs/fsx_cry",
  44. "FSXNET-Ham Radio": "fsx_ham",
  45. # "FSXNET-Magicka": "msgs/fsx_mag",
  46. "FSXNET-Magicka": "fsx_mag",
  47. "FSXNET-Mystic": "fsx_mys",
  48. "FSXNET-Enigma": "fsx_eng",
  49. "FSXNET-Data": "fsx_dat",
  50. # "HappyNet-General": "msgs/hpy_gen",
  51. }
  52. def bbs_get_messages(area):
  53. global dbc
  54. messages = []
  55. for row in dbc.execute(
  56. # "SELECT message_id, to_user_name, from_user_name, subject, modified_timestamp from message WHERE area_tag=?",
  57. "SELECT message_id, to_user_name, from_user_name, subject, modified_timestamp from message WHERE area_tag=? ORDER BY message_id;",
  58. (area,),
  59. ):
  60. stamp = pendulum.parse(row[4]).timestamp()
  61. messages.append(
  62. {
  63. "MsgNum": row[0],
  64. "number": row[0],
  65. "to": row[1],
  66. "from": row[2],
  67. "subject": row[3],
  68. "written": stamp,
  69. # // written
  70. # // received
  71. # // processed
  72. }
  73. )
  74. return messages
  75. MATCH1 = re.compile(">>> BEGIN(.*)>>> END", re.DOTALL)
  76. def bbs_message(area, msgno):
  77. global dbc
  78. messages = []
  79. dbc.execute(
  80. "SELECT message_id, to_user_name, from_user_name, subject, modified_timestamp, message from message WHERE message_id=?",
  81. (msgno,),
  82. )
  83. row = dbc.fetchone()
  84. stamp = pendulum.parse(row[4]).timestamp()
  85. data = {
  86. "MsgNum": row[0],
  87. "number": row[0],
  88. "to": row[1],
  89. "from": row[2],
  90. "subject": row[3],
  91. "written": stamp,
  92. "received": stamp,
  93. "processed": stamp,
  94. "text": row[5], # .decode("cp437"),
  95. "bytes": row[5].encode("cp437")
  96. # // written
  97. # // received
  98. # // processed
  99. }
  100. if (area == 'fsx_dat') and ('>>> BEGIN' in row[5]):
  101. body = row[5] + "\n"
  102. result = MATCH1.search(body)
  103. if result:
  104. data['rot47'] = rot47(result.group(1)).lstrip("\n").replace("\n", "<br />")
  105. return data
  106. # bases = {"FSX_BOT": "fsx_bot"}
  107. # @cache.memoize(timeout=5 * 60, key_prefix="messages")
  108. @cache.memoize(timeout=5 * 60)
  109. def get_messages(base):
  110. messages = bbs_get_messages(base)
  111. messages.reverse()
  112. return messages
  113. @cache.memoize(timeout=60)
  114. def get_message(base, msgno):
  115. # message = jammin.read_message(base, msgno)
  116. message = bbs_message(base, msgno)
  117. return message
  118. @app.errorhandler(404)
  119. def not_found(e):
  120. return render_template("404.html")
  121. @app.route(base_path + "/list")
  122. def list_bases():
  123. return render_template(
  124. "list.html", bases=bases, base_path=base_path, title="Message Areas"
  125. )
  126. # return 'Here would be a listing of message bases'
  127. @app.route(base_path + "/clear")
  128. def clear_cache():
  129. cache.clear()
  130. return "Cache Cleared. Back to hitting refresh!"
  131. @app.route(base_path + "/messages/<area>")
  132. def display_messages(area):
  133. if area not in bases:
  134. return render_template(
  135. "missing-area.html", base_path=base_path, title="Missing Area"
  136. )
  137. # messages = jammin.get_messages(bases[area])
  138. messages = get_messages(bases[area])
  139. # messages.reverse() # cached.reverse()
  140. page = request.args.get(get_page_parameter(), type=int, default=1)
  141. # get_page_arg defaults to page 1, per_page of 10
  142. PER_PAGE = 50
  143. total = len(messages)
  144. pagination = Pagination(
  145. page=page,
  146. total=total,
  147. css_framework="foundation",
  148. record_name="messages",
  149. per_page=PER_PAGE,
  150. )
  151. page, per_page, offset = get_page_args()
  152. start = (page - 1) * PER_PAGE
  153. end = start + PER_PAGE
  154. # messages = messages[(page-1) * PER_PAGE:offset+PER_PAGE]
  155. messages = messages[start:end]
  156. return render_template(
  157. "messages.html",
  158. messages=messages,
  159. area=area,
  160. pagination=pagination,
  161. base_path=base_path,
  162. title="Messages for " + bases[area],
  163. )
  164. @cache.memoize(timeout=60)
  165. def ansi_to_png(raw_ansi_bytes, idx):
  166. pid = os.getppid()
  167. ansifile = "{0}-{1}.ans".format(idx, pid)
  168. pngfile = "{0}-{1}.png".format(idx, pid)
  169. with open(ansifile, "wb") as fp:
  170. fp.write(raw_ansi_bytes)
  171. subprocess.run(["./ansilove", "-d", "-o", pngfile, ansifile])
  172. with open(pngfile, "rb") as fp:
  173. png = fp.read()
  174. os.unlink(ansifile)
  175. os.unlink(pngfile)
  176. return png
  177. def ansi_to_png64(raw_ansi_bytes, idx):
  178. png = ansi_to_png(raw_ansi_bytes, idx)
  179. return base64.b64encode(png).decode("utf-8")
  180. @app.route(base_path + "/image/<area>/<int:msgno>.png")
  181. def display_ansi(area, msgno):
  182. if area not in bases:
  183. return "RATS", 404
  184. message = get_message(bases[area], msgno)
  185. if not message:
  186. return "RATS", 404
  187. if not "text" in message:
  188. return "RATS", 404
  189. # png = ansi_to_png(message["bytes"].replace(b"\r", b"\n"), msgno)
  190. png = ansi_to_png(message["bytes"], msgno)
  191. # png = ansi_to_png(message["bytes"].replace("\r", "\n"), msgno)
  192. response = make_response(png)
  193. response.headers.set("Content-Type", "image/png")
  194. return response
  195. # <img alt="My Image" src="data:image/png;base64,
  196. @app.route(base_path + "/read/<area>/<int:msgno>")
  197. def display_message(area, msgno):
  198. if area not in bases:
  199. return render_template(
  200. "missing-area.html", base_path=base_path, title="Missing Area"
  201. )
  202. # message = jammin.read_message(bases[area], msgno)
  203. message = get_message(bases[area], msgno)
  204. if not message:
  205. return render_template(
  206. "missing-message.html",
  207. base_path=base_path,
  208. area=area,
  209. title="Missing Message",
  210. )
  211. messages = get_messages(bases[area])
  212. # prevmsg and nextmsg are completely different now.
  213. prevmsg = None
  214. nextmsg = None
  215. total = len(messages)
  216. for idx, msg in enumerate(messages):
  217. if msg["MsgNum"] == msgno:
  218. # Ok, found what we're looking for
  219. if idx > 0:
  220. prevmsg = messages[idx - 1]["MsgNum"]
  221. if idx + 1 < total:
  222. nextmsg = messages[idx + 1]["MsgNum"]
  223. # prevmsg = None
  224. # nextmsg = None
  225. # if msgno > 1:
  226. # prevmsg = msgno - 1
  227. # if msgno < total:
  228. # nextmsg = msgno + 1
  229. if "text" in message:
  230. if "\x1b" in message["text"]:
  231. # Ok, the message contains ANSI CODES -- Convert
  232. message["png"] = True
  233. # message["png"] = ansi_to_png64(
  234. # message["bytes"].replace(b"\r", b"\n"), msgno
  235. # )
  236. else:
  237. text = message["text"].replace("\r", "\n")
  238. # Ok, latest changes aren't doing word-wrap for us, so do it here.
  239. text = "\n".join(
  240. [
  241. textwrap.fill(txt, width=78, replace_whitespace=False)
  242. for txt in text.splitlines()
  243. ]
  244. )
  245. message["text"] = text
  246. # message["text"].replace("\r", "\n") # <br >\n")
  247. return render_template(
  248. "message.html",
  249. message=message,
  250. area=area,
  251. msgnumber=msgno,
  252. prevmsg=prevmsg,
  253. nextmsg=nextmsg,
  254. base_path=base_path,
  255. title="Message {0}".format(msgno),
  256. )