"nom",
]
-[[package]]
-name = "async-compression"
-version = "0.4.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5"
-dependencies = [
- "flate2",
- "futures-core",
- "memchr",
- "pin-project-lite",
- "tokio",
-]
-
[[package]]
name = "async-trait"
version = "0.1.80"
"unicode_categories",
]
-[[package]]
-name = "crc32fast"
-version = "1.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
-dependencies = [
- "cfg-if",
-]
-
[[package]]
name = "deunicode"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
-[[package]]
-name = "flate2"
-version = "1.0.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
-dependencies = [
- "crc32fast",
- "miniz_oxide",
-]
-
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
dependencies = [
- "async-compression",
"bitflags",
"bytes",
- "futures-core",
"futures-util",
"http",
"http-body",
axum = { version = "0.7", features = ["http2"] }
tokio = { version = "1", features = ["full"] }
tower = { version = "0.4", features = ["util"] }
-tower-http = { version = "0.5", features = ["fs", "trace", "compression-gzip"] }
+tower-http = { version = "0.5", features = ["fs", "trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
--- /dev/null
+# Systemd
+
+"/doc/mumble_web.service" is a systemd user service file. On linux it must be placed
+in "~/.config/systemd/user".
+
+# Documentation
+
+Axum: https://docs.rs/axum/latest/axum/index.html
+Axum examples: https://github.com/tokio-rs/axum/blob/main/ECOSYSTEM.md
+Tower HTTP: https://docs.rs/tower-http/0.5.2/tower_http/index.html
+Askama: https://djc.github.io/askama/askama.html
--- /dev/null
+[Unit]
+Description=mumble_web
+
+[Service]
+WorkingDirectory=/var/www/mumble_web
+ExecStart=/var/www/mumble_web/mumble_web
+SyslogIdentifier=mumble_web
+Restart=always
+RestartSec=10
+KillSignal=SIGINT
+
+[Install]
+WantedBy=default.target
initdata.properties = Ice.createProperties(None, initdata.properties)
initdata.properties.setProperty('Ice.ImplicitContext', 'Shared')
-def getChannelPath(channels, channelId):
+def getChannelPath(channels, channelId, first=True):
if channelId == 0:
- return ""
+ return "/"
else:
channel = channels[channelId]
- return getChannelPath(channels, channel.parent) + "/" + channel.name
+ return getChannelPath(channels, channel.parent, False) + channel.name + \
+ ("" if first else "/")
def getUsers(server):
- return map(lambda u: (u.name, u.channel), server.getUsers().values())
+ return map(lambda u: (u.name, u.channel, u.selfMute, u.selfDeaf), server.getUsers().values())
def getUsersAsJson(server):
channels = server.getChannels()
users = getUsers(server)
return json.dumps(
[
- {'username': username, 'channel': getChannelPath(channels, channel)}
- for (username, channel) in users
+ {
+ 'username': username,
+ 'channel': getChannelPath(channels, channel),
+ 'selfMute': selfMute,
+ 'selfDeaf': selfDeaf
+ }
+ for (username, channel, selfMute, selfDeaf) in users
]
)
pub struct User {
pub name: String,
pub channel: String,
+ pub self_mute: bool,
+ pub self_deaf: bool, // 🔇
}
pub struct Ice {
users.push(User {
name: user_json["username"].as_str().unwrap().to_string(),
channel: user_json["channel"].as_str().unwrap().to_string(),
+ self_mute: user_json["selfMute"].as_bool().unwrap(),
+ self_deaf: user_json["selfDeaf"].as_bool().unwrap(),
});
}
self.cached_users = users.clone();
impl Drop for Ice {
fn drop(&mut self) {
- println!("Dropping..",);
+ // println!("Ice: dropping...",);
self.dropped.store(true, Ordering::Relaxed);
self.kill_event.set();
self.kill_event.wait();
- println!("Dropped!",);
+ // println!("Ice: dropped!",);
}
}
use askama_axum::IntoResponse;
use axum::{extract::State, routing::get, Router};
use tokio::signal;
-use tower_http::{compression::CompressionLayer, services::ServeDir};
+use tower_http::services::ServeDir;
mod ice;
#[derive(Eq)]
struct User {
name: String,
+ self_mute: bool,
+ self_deaf: bool,
+}
+
+impl User {
+ fn from_ice_user(user: &ice::User) -> Self {
+ User {
+ name: user.name.clone(),
+ self_mute: user.self_mute,
+ self_deaf: user.self_deaf,
+ }
+ }
}
impl PartialEq for User {
let app = Router::new()
.route("/", get(root))
.nest_service("/static", ServeDir::new("static"))
- .with_state(ice)
- .layer(CompressionLayer::new());
+ .with_state(ice);
let addr = SocketAddr::from(([127, 0, 0, 1], 8086));
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
'next_user: for u in &users {
for c in &mut channels {
if c.name == u.channel {
- c.users.push(User {
- name: u.name.clone(),
- });
+ c.users.push(User::from_ice_user(u));
continue 'next_user;
}
}
let channel = Channel {
name: u.channel.clone(),
- users: vec![User {
- name: u.name.clone(),
- }],
+ users: vec![User::from_ice_user(u)],
};
channels.push(channel);
}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
+<g opacity="0.85">
+ <path fill="#EA4335" d="M168.46,169.386c0-12.6-10.309-22.909-22.909-22.909h-10.19c-12.6,0-22.909,10.309-22.909,22.909v21.048
+ v53.296l56.008-28.068V169.386z"/>
+</g>
+<path opacity="0.85" fill="#EA4335" enable-background="new " d="M391.982,103.627l-198.147,99.316l-0.001-33.063
+ c0-12.6,9.309-27.338,20.687-32.751l156.772-74.583c11.379-5.413,20.687,0.079,20.687,12.203L391.982,103.627z"/>
+<path opacity="0.85" fill="#EA4335" enable-background="new " d="M193.835,297.054v44.058c0,12.6,9.309,27.338,20.687,32.751
+ l156.771,74.583c11.379,5.413,20.687-0.467,20.687-13.067l0.002-237.018l73.56-35.201l-19.709-39.323L46.46,324.009l19.709,39.323
+ l46.282-24.613v4.721c0,12.6,10.309,22.909,22.909,22.909h10.19c12.6,0,22.909-10.309,22.909-22.909v-32.799L193.835,297.054z"/>
+</svg>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="512px" height="512px" viewBox="0 0 512 512" enable-background="new 0 0 512 512" xml:space="preserve">
+<path opacity="0.85" fill="#EA4335" enable-background="new " d="M256,55.05c-41.43,0-75.02,33.59-75.02,75.02v100.89
+ l150.04-81.8v-19.09C331.02,88.64,297.43,55.05,256,55.05z"/>
+<path opacity="0.85" fill="#FF3E3E" enable-background="new " d="M237.46,444.93L237.46,444.93z"/>
+<path opacity="0.85" fill="#FF3E3E" enable-background="new " d="M274.54,444.93L274.54,444.93z"/>
+<path opacity="0.85" fill="#EA4335" enable-background="new " d="M274.54,444.93c62.37-8.99,110.29-62.66,110.29-127.51
+ l-26.71,0.79c0,56.4-45.72,102.12-102.12,102.12c-44.12,0-81.71-27.98-95.97-67.17l-0.001-0.002l27.46-14.971l0.001,0.003
+ c11.71,26.19,37.98,44.43,68.51,44.43c41.43,0,75.02-33.59,75.02-75.02l0.001-47.667L481.5,177.89l-22.98-42.14L30.5,369.11
+ l22.98,42.14l83.149-45.333l0.001,0.003c16.91,41.62,54.96,72.39,100.83,79.01v27.24h-73.4c-10.41,0-18.85,8.44-18.85,18.85v-2.93
+ c0,10.42,8.44,18.86,18.85,18.86h183.88c10.41,0,18.85-8.44,18.85-18.86v2.93c0-10.41-8.44-18.85-18.85-18.85h-73.4V444.93z"/>
+</svg>
background-color: #252525;
}
-#channel {
+.channel {
color: #c5c500;
}
+.icon {
+ height: 20px;
+ margin-left: 5px;
+ margin-right: 5px;
+ vertical-align: bottom;
+}
+
h1 {
font-size: x-large;
margin: 20px 20px 16px 20px;
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Euphoirik Mumble</title>
+ <title>Euphorik Mumble</title>
<link rel="stylesheet" type="text/css" href="static/style.css" />
</head>
<p>No user connected</p>
{% else %}
{% for channel in channels %}
- <h2 id="channel">{{ channel.name }}</h2>
+ <h2 class="channel">{{ channel.name }}</h2>
<ul>
{% for user in channel.users %}
- <li>{{ user.name }}</li>
+ <li>{{ user.name }}
+ {% if user.self_mute %}<img class="icon" src="static/muted_self.svg" />{% endif %}<!--
+ -->{% if user.self_deaf %}<img class="icon" src="static/deafened_self.svg" />{% endif %}
+ </li>
{% endfor %}
</ul>
{% endfor %}