app.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. #!/usr/bin/env python3
  2. from telnetlib import Telnet
  3. import toml
  4. from flask import Flask, jsonify, render_template_string
  5. app = Flask(__name__)
  6. def get_users():
  7. with open("secret.toml") as infile:
  8. cfg = toml.load(infile)
  9. login = ("login %s %s\n" % (cfg["user"], cfg["pass"])).encode("utf-8")
  10. with Telnet(cfg["host"], cfg["port"], 5) as tn:
  11. print("connection")
  12. print(tn.read_until(b"\n").decode("ascii"))
  13. print(tn.read_until(b"\n").decode("ascii"))
  14. print("----")
  15. tn.write(login)
  16. print("after login")
  17. print(tn.read_until(b"\n").decode("ascii"))
  18. print("----")
  19. tn.write(b"use 1 -virtual\n")
  20. print("after use")
  21. print(tn.read_until(b"\n").decode("ascii"))
  22. print("----")
  23. tn.write(b"clientlist\n")
  24. response = tn.read_until(b"\n").decode("ascii")
  25. print("after clientlist")
  26. print(response)
  27. print(tn.read_until(b"\n").decode("ascii"))
  28. print("----")
  29. # entries separated by |'s
  30. entries = response.split("|")
  31. # entries contain key=value pairs separated by spaces
  32. pairs = [[pr.split("=", 1) for pr in ent.split()] for ent in entries]
  33. # rearrange these into maps for convenience
  34. entry_maps = [{k: v for k, v in pr} for pr in pairs]
  35. # combine the maps into one large map, ignoring serveradmin query user
  36. client_info = {info["client_nickname"]: info for info in entry_maps if "serveradmin" not in info["client_nickname"]}
  37. for k, v in client_info.items():
  38. tn.write(f"clientinfo clid={v['clid']}\n".encode("utf-8"))
  39. response = tn.read_until(b"\n").decode("ascii")
  40. print(f"after clientinfo for {k}")
  41. print(response)
  42. print(tn.read_until(b"\n").decode("ascii"))
  43. print("----")
  44. # info is key=value pairs separated by spaces
  45. pairs = [ent.split("=", 1) for ent in response.split() if "=" in ent]
  46. # rearrange into a map and put in the client_info
  47. v["client_info"] = {k: v for k, v in pairs}
  48. tn.write(b"quit\n")
  49. print("after quit")
  50. print(tn.read_until(b"\n").decode("ascii"))
  51. users = []
  52. for name, info in client_info.items():
  53. user_text = name
  54. audio_status = []
  55. if info["client_info"].get("client_input_muted", "0") == "1":
  56. audio_status.append("Mic muted")
  57. if info["client_info"].get("client_output_muted", "0") == "1":
  58. audio_status.append("Sound muted")
  59. if len(audio_status) > 0:
  60. user_text += f" ({', '.join(audio_status)})"
  61. users.append(user_text)
  62. return users
  63. @app.route("/")
  64. def get_status():
  65. return jsonify({"users": get_users()})
  66. @app.route("/page")
  67. def get_status_page():
  68. # JS adapted from
  69. # https://stackoverflow.com/questions/6152522/how-can-i-make-a-paragraph-bounce-around-in-a-div
  70. return render_template_string("""
  71. <!doctype html>
  72. <title>Teamspeak Server Status</title>
  73. <style>
  74. body {
  75. margin: 0px 0px 0px 0px;
  76. padding: 0px 0px 0px 0px;
  77. font-family: verdana, arial, helvetica, sans-serif;
  78. color: #ccc;
  79. background-color: #333;
  80. }
  81. h1 {
  82. font-size: 24px;
  83. line-height: 44px;
  84. font-weight: bold;
  85. margin-top: 0;
  86. margin-bottom: 0;
  87. }
  88. #bounceBox {
  89. position: absolute;
  90. top: 0;
  91. right: 0;
  92. bottom: 0;
  93. left: 0;
  94. }
  95. #bouncer {
  96. position: absolute;
  97. }
  98. </style>
  99. <script>
  100. let vx = 5;
  101. let vy = 3;
  102. const buffer = 5;
  103. const hitLR = (el, bounding) => {
  104. if (el.offsetLeft <= buffer && vx < 0) {
  105. vx = -1 * vx;
  106. }
  107. if ((el.offsetLeft + el.offsetWidth) >= (bounding.offsetWidth - buffer)) {
  108. vx = -1 * vx;
  109. }
  110. if (el.offsetTop <= buffer && vy < 0) {
  111. vy = -1 * vy;
  112. }
  113. if ((el.offsetTop + el.offsetHeight) >= (bounding.offsetHeight - buffer)) {
  114. vy = -1 * vy;
  115. }
  116. }
  117. const mover = (el, bounding) => {
  118. hitLR(el, bounding);
  119. el.style.left = el.offsetLeft + vx + 'px';
  120. el.style.top = el.offsetTop + vy + 'px';
  121. setTimeout(function() {
  122. mover(el, bounding);
  123. }, 50);
  124. }
  125. setTimeout(() => mover(
  126. document.getElementById("bouncer"),
  127. document.getElementById("bounceBox")
  128. ), 50);
  129. </script>
  130. <body>
  131. <div id="bounceBox">
  132. <div id="bouncer">
  133. <h1>TeamSpeak Server Status</h1>
  134. {% if users|length == 0 %}
  135. No one in teamspeak!
  136. {% elif users|length == 1 %}
  137. Only {{ users[0] }}
  138. {% else %}
  139. <ul>
  140. {% for user in users %}
  141. <li>{{ user }}</li>
  142. {% endfor %}
  143. </ul>
  144. {% endif %}
  145. </div>
  146. </div>
  147. </body>
  148. """, users=sorted(get_users()))
  149. if __name__ == "__main__":
  150. app.run("0.0.0.0", 5000, debug=True, threaded=True)