new modes
This commit is contained in:
33
server.py
33
server.py
@@ -34,8 +34,8 @@ ALLOWED_CHARS = string.ascii_letters + string.digits + '_-'
|
||||
# Hidden dial codes - kept server-side so the client JS bundle never reveals them.
|
||||
# Sequence the user types on the in-app dialpad maps to a server action.
|
||||
DIAL_CODES = {
|
||||
'*4201#': 'trippy_on',
|
||||
'*4202#': 'trippy_off',
|
||||
'*420#': 'trippy_toggle', # toggles UI trippy mode globally for everyone
|
||||
'*1337#': 'rainbow_nick_toggle', # toggles the dialer's own rainbow nick
|
||||
}
|
||||
DIAL_MAX_LEN = 32
|
||||
|
||||
@@ -211,7 +211,7 @@ async def websocket_handler(request: web.Request) -> web.WebSocketResponse:
|
||||
await ws.prepare(request)
|
||||
|
||||
client_id = str(uuid.uuid4())[:8]
|
||||
clients[client_id] = {'ws': ws, 'username': None, 'cam_on': False, 'mic_on': True, 'screen_on': False}
|
||||
clients[client_id] = {'ws': ws, 'username': None, 'cam_on': False, 'mic_on': True, 'screen_on': False, 'rainbow_nick': False}
|
||||
|
||||
logging.info(f'[{client_id}] Connected')
|
||||
|
||||
@@ -274,7 +274,7 @@ async def handle_message(client_id: str, data: dict):
|
||||
reconnect_tokens[reconnect_token] = {'username': username, 'expires': time.time() + 3600}
|
||||
|
||||
users = [
|
||||
{'id': cid, 'username': c['username'], 'cam_on': c.get('cam_on', False), 'mic_on': c.get('mic_on', True), 'screen_on': c.get('screen_on', False)}
|
||||
{'id': cid, 'username': c['username'], 'cam_on': c.get('cam_on', False), 'mic_on': c.get('mic_on', True), 'screen_on': c.get('screen_on', False), 'rainbow_nick': c.get('rainbow_nick', False)}
|
||||
for cid, c in clients.items()
|
||||
if c['username'] and cid != client_id
|
||||
]
|
||||
@@ -338,7 +338,7 @@ async def handle_message(client_id: str, data: dict):
|
||||
reconnect_tokens[new_token] = {'username': username, 'expires': time.time() + 3600}
|
||||
|
||||
users = [
|
||||
{'id': cid, 'username': c['username'], 'cam_on': c.get('cam_on', False), 'mic_on': c.get('mic_on', True), 'screen_on': c.get('screen_on', False)}
|
||||
{'id': cid, 'username': c['username'], 'cam_on': c.get('cam_on', False), 'mic_on': c.get('mic_on', True), 'screen_on': c.get('screen_on', False), 'rainbow_nick': c.get('rainbow_nick', False)}
|
||||
for cid, c in clients.items()
|
||||
if c['username'] and cid != client_id
|
||||
]
|
||||
@@ -428,14 +428,21 @@ async def handle_message(client_id: str, data: dict):
|
||||
if len(sequence) > DIAL_MAX_LEN:
|
||||
return
|
||||
action = DIAL_CODES.get(sequence)
|
||||
if action == 'trippy_on' and not trippy_mode:
|
||||
trippy_mode = True
|
||||
logging.info(f'[{client_id}] Trippy mode ENABLED')
|
||||
await broadcast_all({'type': 'trippy_status', 'enabled': True})
|
||||
elif action == 'trippy_off' and trippy_mode:
|
||||
trippy_mode = False
|
||||
logging.info(f'[{client_id}] Trippy mode DISABLED')
|
||||
await broadcast_all({'type': 'trippy_status', 'enabled': False})
|
||||
if action == 'trippy_toggle':
|
||||
trippy_mode = not trippy_mode
|
||||
logging.info(f'[{client_id}] Trippy mode -> {trippy_mode}')
|
||||
await broadcast_all({'type': 'trippy_status', 'enabled': trippy_mode})
|
||||
elif action == 'rainbow_nick_toggle':
|
||||
# Per-user toggle: only flips the dialer's own nick. Broadcast so every
|
||||
# other client renders the rainbow effect on this user in their list.
|
||||
current = clients[client_id].get('rainbow_nick', False)
|
||||
clients[client_id]['rainbow_nick'] = not current
|
||||
logging.info(f'[{client_id}] Rainbow nick -> {not current}')
|
||||
await broadcast_all({
|
||||
'type' : 'nick_status',
|
||||
'id' : client_id,
|
||||
'rainbow' : not current
|
||||
})
|
||||
|
||||
|
||||
async def broadcast(sender_id: str, message: dict):
|
||||
|
||||
@@ -344,11 +344,18 @@ function handleSignal(data) {
|
||||
|
||||
// Preserve local state on reconnect, or initialize
|
||||
if (!state.users['local']) {
|
||||
state.users['local'] = { username: state.username, camOn: state.camEnabled, micOn: state.micEnabled, screenOn: state.screenEnabled, speaking: false };
|
||||
state.users['local'] = { username: state.username, camOn: state.camEnabled, micOn: state.micEnabled, screenOn: state.screenEnabled, rainbowNick: false, speaking: false };
|
||||
}
|
||||
|
||||
data.users.forEach(user => {
|
||||
state.users[user.id] = { username: user.username, camOn: user.cam_on, micOn: user.mic_on !== false, screenOn: user.screen_on || false, speaking: false };
|
||||
state.users[user.id] = {
|
||||
username: user.username,
|
||||
camOn: user.cam_on,
|
||||
micOn: user.mic_on !== false,
|
||||
screenOn: user.screen_on || false,
|
||||
rainbowNick: !!user.rainbow_nick,
|
||||
speaking: false
|
||||
};
|
||||
createPeerConnection(user.id, user.username, true);
|
||||
});
|
||||
|
||||
@@ -484,6 +491,17 @@ function handleSignal(data) {
|
||||
setTrippyMode(!!data.enabled);
|
||||
break;
|
||||
|
||||
case 'nick_status':
|
||||
// Per-user rainbow nick toggle. Server tells us when ANY user (including
|
||||
// us) flips theirs - we just mirror it into local state and re-render.
|
||||
if (data.id === state.myId) {
|
||||
if (state.users['local']) state.users['local'].rainbowNick = !!data.rainbow;
|
||||
} else if (state.users[data.id]) {
|
||||
state.users[data.id].rainbowNick = !!data.rainbow;
|
||||
}
|
||||
updateUsersList();
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ function updateUsersList() {
|
||||
<div class="user-item ${user.speaking ? 'speaking' : ''} ${user.isLocal ? 'local' : ''}" data-id="${user.id}">
|
||||
<div class="user-info">
|
||||
${user.isLocal ? '' : getNetworkQualityHTML(user.id)}
|
||||
<span class="user-name">${escapeHtml(user.username)}</span>
|
||||
<span class="user-name${user.rainbowNick ? ' rainbow-nick' : ''}">${escapeHtml(user.username)}</span>
|
||||
<div class="user-indicators">
|
||||
${user.micOn === false ? '<svg class="indicator mic-muted" viewBox="0 0 24 24" fill="currentColor" title="Muted"><path d="M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM4.27 3L3 4.27l6.01 6.01V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z"/></svg>' : ''}
|
||||
${user.camOn ? '<svg class="indicator cam-on" viewBox="0 0 24 24" fill="currentColor" title="Camera On"><path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/></svg>' : ''}
|
||||
|
||||
@@ -1968,3 +1968,34 @@ body.trippy-mode .video-tile {
|
||||
body.trippy-mode .video-tile video {
|
||||
animation: trippy-hue-counter 12s linear infinite;
|
||||
}
|
||||
|
||||
/* ========== RAINBOW NICK ==========
|
||||
* Per-user toggle (server-side, broadcast to everyone). Renders the username text
|
||||
* as a flowing rainbow gradient. Works underneath trippy-mode too - the trippy
|
||||
* filter just hue-shifts the rainbow, which still looks rainbow. */
|
||||
.rainbow-nick {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
hsl(0, 90%, 60%),
|
||||
hsl(45, 95%, 60%),
|
||||
hsl(90, 85%, 55%),
|
||||
hsl(135, 80%, 55%),
|
||||
hsl(180, 85%, 55%),
|
||||
hsl(225, 90%, 65%),
|
||||
hsl(270, 85%, 65%),
|
||||
hsl(315, 90%, 60%),
|
||||
hsl(360, 90%, 60%)
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
-webkit-text-fill-color: transparent;
|
||||
animation: rainbow-nick-shift 3s linear infinite;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@keyframes rainbow-nick-shift {
|
||||
0% { background-position: 0% 50%; }
|
||||
100% { background-position: 200% 50%; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user