messages.py 7.9 KB

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