|
@@ -1,6 +1,7 @@
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
from telnetlib import Telnet
|
|
|
+from collections import defaultdict
|
|
|
|
|
|
import toml
|
|
|
from flask import Flask, jsonify, render_template_string
|
|
@@ -8,6 +9,16 @@ from flask import Flask, jsonify, render_template_string
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
|
|
|
+def parse_ts_response(response):
|
|
|
+ # entries separated by |'s
|
|
|
+ entries = response.split("|")
|
|
|
+ # entries contain key=value pairs separated by spaces
|
|
|
+ pairs = [[pr.split("=", 1) for pr in ent.split()] for ent in entries]
|
|
|
+ # rearrange these into maps for convenience
|
|
|
+ return [{k: v for k, v in pr} for pr in pairs]
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
def get_users():
|
|
|
with open("secret.toml") as infile:
|
|
|
cfg = toml.load(infile)
|
|
@@ -16,42 +27,48 @@ def get_users():
|
|
|
|
|
|
with Telnet(cfg["host"], cfg["port"], 5) as tn:
|
|
|
print("connection")
|
|
|
- print(tn.read_until(b"\n").decode("ascii"))
|
|
|
- print(tn.read_until(b"\n").decode("ascii"))
|
|
|
+ print(tn.read_until(b"\n").decode("utf-8"))
|
|
|
+ print(tn.read_until(b"\n").decode("utf-8"))
|
|
|
print("----")
|
|
|
|
|
|
tn.write(login)
|
|
|
print("after login")
|
|
|
- print(tn.read_until(b"\n").decode("ascii"))
|
|
|
+ print(tn.read_until(b"\n").decode("utf-8"))
|
|
|
print("----")
|
|
|
|
|
|
tn.write(b"use 1 -virtual\n")
|
|
|
print("after use")
|
|
|
- print(tn.read_until(b"\n").decode("ascii"))
|
|
|
+ print(tn.read_until(b"\n").decode("utf-8"))
|
|
|
print("----")
|
|
|
|
|
|
+ tn.write(b"channellist\n")
|
|
|
+ response = tn.read_until(b"\n").decode("utf-8")
|
|
|
+ print("after channellist")
|
|
|
+ print(response)
|
|
|
+ print(tn.read_until(b"\n").decode("utf-8"))
|
|
|
+ print("----")
|
|
|
+
|
|
|
+ channel_maps = parse_ts_response(response)
|
|
|
+ # rearrange the maps into one large channel lookup map
|
|
|
+ channels = {info["cid"]: info["channel_name"].replace(r"\s", " ") for info in channel_maps}
|
|
|
+
|
|
|
tn.write(b"clientlist\n")
|
|
|
- response = tn.read_until(b"\n").decode("ascii")
|
|
|
+ response = tn.read_until(b"\n").decode("utf-8")
|
|
|
print("after clientlist")
|
|
|
print(response)
|
|
|
- print(tn.read_until(b"\n").decode("ascii"))
|
|
|
+ print(tn.read_until(b"\n").decode("utf-8"))
|
|
|
print("----")
|
|
|
|
|
|
- # entries separated by |'s
|
|
|
- entries = response.split("|")
|
|
|
- # entries contain key=value pairs separated by spaces
|
|
|
- pairs = [[pr.split("=", 1) for pr in ent.split()] for ent in entries]
|
|
|
- # rearrange these into maps for convenience
|
|
|
- entry_maps = [{k: v for k, v in pr} for pr in pairs]
|
|
|
+ entry_maps = parse_ts_response(response)
|
|
|
# combine the maps into one large map, ignoring serveradmin query user
|
|
|
client_info = {info["client_nickname"]: info for info in entry_maps if "serveradmin" not in info["client_nickname"]}
|
|
|
|
|
|
for k, v in client_info.items():
|
|
|
tn.write(f"clientinfo clid={v['clid']}\n".encode("utf-8"))
|
|
|
- response = tn.read_until(b"\n").decode("ascii")
|
|
|
+ response = tn.read_until(b"\n").decode("utf-8")
|
|
|
print(f"after clientinfo for {k}")
|
|
|
print(response)
|
|
|
- print(tn.read_until(b"\n").decode("ascii"))
|
|
|
+ print(tn.read_until(b"\n").decode("utf-8"))
|
|
|
print("----")
|
|
|
|
|
|
# info is key=value pairs separated by spaces
|
|
@@ -61,10 +78,11 @@ def get_users():
|
|
|
|
|
|
tn.write(b"quit\n")
|
|
|
print("after quit")
|
|
|
- print(tn.read_until(b"\n").decode("ascii"))
|
|
|
+ print(tn.read_until(b"\n").decode("utf-8"))
|
|
|
|
|
|
|
|
|
users = []
|
|
|
+ channel_users = defaultdict(list)
|
|
|
for name, info in client_info.items():
|
|
|
user_text = name
|
|
|
audio_status = []
|
|
@@ -75,13 +93,14 @@ def get_users():
|
|
|
if len(audio_status) > 0:
|
|
|
user_text += f" ({', '.join(audio_status)})"
|
|
|
users.append(user_text)
|
|
|
+ channel_users[channels[info["cid"]]].append(user_text)
|
|
|
|
|
|
- return users
|
|
|
+ return sorted(users), {k: sorted(v) for k, v in channel_users.items()}
|
|
|
|
|
|
|
|
|
@app.route("/")
|
|
|
def get_status():
|
|
|
- return jsonify({"users": get_users()})
|
|
|
+ return jsonify({"users": get_users()[0]})
|
|
|
|
|
|
|
|
|
@app.route("/page")
|
|
@@ -106,6 +125,13 @@ def get_status_page():
|
|
|
margin-top: 0;
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
+ h2 {
|
|
|
+ font-size: 18px;
|
|
|
+ line-height: 20px;
|
|
|
+ margin-top: 0;
|
|
|
+ margin-left: 15px;
|
|
|
+ margin-bottom: -10px;
|
|
|
+ }
|
|
|
#bounceBox {
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
@@ -157,19 +183,20 @@ def get_status_page():
|
|
|
<h1>TeamSpeak Server Status</h1>
|
|
|
{% if users|length == 0 %}
|
|
|
No one in teamspeak!
|
|
|
- {% elif users|length == 1 %}
|
|
|
- Only {{ users[0] }}
|
|
|
{% else %}
|
|
|
- <ul>
|
|
|
- {% for user in users %}
|
|
|
- <li>{{ user }}</li>
|
|
|
- {% endfor %}
|
|
|
- </ul>
|
|
|
+ {% for channel, people in users.items() %}
|
|
|
+ <h2>{{ channel }}</h2>
|
|
|
+ <ul>
|
|
|
+ {% for user in people %}
|
|
|
+ <li>{{ user }}</li>
|
|
|
+ {% endfor %}
|
|
|
+ </ul>
|
|
|
+ {% endfor %}
|
|
|
{% endif %}
|
|
|
</div>
|
|
|
</div>
|
|
|
</body>
|
|
|
- """, users=sorted(get_users()))
|
|
|
+ """, users=get_users()[1])
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|