app.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. #!/usr/bin/env python3
  2. import tempfile
  3. import os.path
  4. from telnetlib import Telnet
  5. from collections import defaultdict
  6. from datetime import timedelta
  7. import toml
  8. from flask import Flask, jsonify, render_template, request
  9. from flask_cors import CORS
  10. IDLE_TIMEOUT = timedelta(minutes=5)
  11. REFRESH_RATE = 60 # seconds
  12. app = Flask(__name__)
  13. CORS(app)
  14. with open("secret.toml") as infile:
  15. cfg = toml.load(infile)
  16. for k in ("host", "port", "user", "pass"):
  17. app.config[k] = cfg[k]
  18. def parse_ts_response(response):
  19. # entries separated by |'s
  20. entries = response.split("|")
  21. # entries contain key=value pairs separated by spaces
  22. pairs = [[pr.split("=", 1) for pr in ent.split()] for ent in entries]
  23. # rearrange these into maps for convenience
  24. return [{k: v for k, v in pr} for pr in pairs]
  25. def login(tn, username, password):
  26. login = f"login {username} {password}\n".encode("utf-8")
  27. print("connection")
  28. print(tn.read_until(b"\n").decode("utf-8"))
  29. print(tn.read_until(b"\n").decode("utf-8"))
  30. print("----")
  31. tn.write(login)
  32. print("after login")
  33. print(tn.read_until(b"\n").decode("utf-8"))
  34. print("----")
  35. tn.write(b"use 1 -virtual\n")
  36. print("after use")
  37. print(tn.read_until(b"\n").decode("utf-8"))
  38. print("----")
  39. def get_client_info(tn):
  40. tn.write(b"clientlist\n")
  41. response = tn.read_until(b"\n").decode("utf-8")
  42. print("after clientlist")
  43. print(response)
  44. print(tn.read_until(b"\n").decode("utf-8"))
  45. print("----")
  46. entry_maps = parse_ts_response(response)
  47. # combine the maps into one large map, ignoring serveradmin query user
  48. return {info["client_nickname"]: info for info in entry_maps if "serveradmin" not in info["client_nickname"]}
  49. def post_to_channel(host, port, username, password, message):
  50. message_sanitized = message.strip().replace(" ", r"\s")
  51. with Telnet(host, port, 5) as tn:
  52. login(tn, username, password)
  53. client_info = get_client_info(tn)
  54. audiobot_id = client_info["AudioBot"]["clid"]
  55. tn.write(f"sendtextmessage targetmode=1 target={audiobot_id} msg={message_sanitized}\n".encode("utf-8"))
  56. # tn.write(f"sendtextmessage targetmode=1 target={audiobot_id} msg=!clear\n".encode("utf-8"))
  57. tn.write(b"quit\n")
  58. def query_ts(host, port, username, password):
  59. with Telnet(host, port, 5) as tn:
  60. login(tn, username, password)
  61. tn.write(b"channellist\n")
  62. response = tn.read_until(b"\n").decode("utf-8")
  63. print("after channellist")
  64. print(response)
  65. print(tn.read_until(b"\n").decode("utf-8"))
  66. print("----")
  67. channel_maps = parse_ts_response(response)
  68. # rearrange the maps into one large channel lookup map
  69. channels = {info["cid"]: info["channel_name"].replace(r"\s", " ") for info in channel_maps}
  70. client_info = get_client_info(tn)
  71. for k, v in client_info.items():
  72. tn.write(f"clientinfo clid={v['clid']}\n".encode("utf-8"))
  73. response = tn.read_until(b"\n").decode("utf-8")
  74. print(f"after clientinfo for {k}")
  75. print(response)
  76. print(tn.read_until(b"\n").decode("utf-8"))
  77. print("----")
  78. # info is key=value pairs separated by spaces
  79. pairs = [ent.split("=", 1) for ent in response.split() if "=" in ent]
  80. # rearrange into a map and put in the client_info
  81. v["client_info"] = {k: v for k, v in pairs}
  82. tn.write(b"quit\n")
  83. print("after quit")
  84. print(tn.read_until(b"\n").decode("utf-8"))
  85. return client_info, channels
  86. def get_users():
  87. client_info, channels = query_ts(
  88. app.config["host"],
  89. app.config["port"],
  90. app.config["user"],
  91. app.config["pass"]
  92. )
  93. users = []
  94. channel_users = defaultdict(list)
  95. for name, info in client_info.items():
  96. user_text = name
  97. audio_status = []
  98. if info["client_info"].get("client_input_muted", "0") == "1":
  99. audio_status.append("Mic muted")
  100. if info["client_info"].get("client_output_muted", "0") == "1":
  101. audio_status.append("Sound muted")
  102. if len(audio_status) > 0:
  103. user_text += f" ({', '.join(audio_status)})"
  104. idle = timedelta(milliseconds=int(info["client_info"].get("client_idle_time", "0")))
  105. if idle >= IDLE_TIMEOUT:
  106. # strip out the sub-second resolution
  107. idle -= timedelta(microseconds=idle.microseconds)
  108. user_text += f" (Idle for {idle})"
  109. users.append(user_text)
  110. channel_users[channels[info["cid"]]].append(user_text)
  111. return sorted(users), {k: sorted(v) for k, v in channel_users.items()}
  112. @app.route("/")
  113. def get_status():
  114. return jsonify({"users": get_users()[0]})
  115. @app.route("/page")
  116. def get_status_page():
  117. return render_template("embedded_body.html", users=get_users()[1], refresh_rate=REFRESH_RATE)
  118. @app.route("/audiobot", methods=["POST"])
  119. def message_audiobot():
  120. temp_dir = tempfile.mkdtemp()
  121. temp_file = os.path.join(temp_dir, "received.mp3")
  122. with open(temp_file, "wb") as f:
  123. f.write(request.data)
  124. f.close()
  125. post_to_channel(
  126. app.config["host"],
  127. app.config["port"],
  128. app.config["user"],
  129. app.config["pass"],
  130. f"!play {temp_file}"
  131. )
  132. return "", 204
  133. if __name__ == "__main__":
  134. app.run("0.0.0.0", 5000, debug=True, threaded=True)