Open to any network now

This commit is contained in:
2026-02-14 17:52:21 -05:00
parent 3db8943e23
commit 4aea93ed02
3 changed files with 220 additions and 64 deletions

View File

@@ -1,14 +1,15 @@
(function () {
'use strict';
const SERVER_URL = 'wss://irc.supernets.org:7000';
const BLACKHOLE = '#blackhole';
const AUTO_JOIN = ['#dev', '#comms', '#exchange', '#hardchats', '#scroll', '#superbowl'];
const AUTO_JOIN_FOCUS = '#superbowl';
const NICK_MAX = 10;
// --- State ---
let nick = '';
let serverHost = '';
let serverPort = 6697;
let useSSL = true;
let autoJoinChannels = [];
let ws = null;
let registered = false;
let activeWindow = 'Status';
@@ -98,10 +99,15 @@
};
// --- DOM ---
const loginEl = document.getElementById('login');
const loginNickEl = document.getElementById('login-nick');
const loginBtnEl = document.getElementById('login-btn');
const appEl = document.getElementById('app');
const loginEl = document.getElementById('login');
const loginServerEl = document.getElementById('login-server');
const loginPortEl = document.getElementById('login-port');
const loginSSLEl = document.getElementById('login-ssl');
const loginNickEl = document.getElementById('login-nick');
const loginChannelsEl = document.getElementById('login-channels');
const loginRememberEl = document.getElementById('login-remember');
const loginBtnEl = document.getElementById('login-btn');
const appEl = document.getElementById('app');
const channelsEl = document.getElementById('channels');
const topicbarEl = document.getElementById('topicbar');
const messagesEl = document.getElementById('messages');
@@ -240,7 +246,20 @@
}
if (spanOpen) out += '</span>';
return out;
return linkify(out);
}
// --- Linkify URLs in HTML ---
function linkify(html) {
// Match URLs - must be careful not to match inside existing HTML tags
const urlRegex = /(?:^|[^"'>])(https?:\/\/[^\s<]+|www\.[^\s<]+)/gi;
return html.replace(urlRegex, function(match, url) {
// If match starts with a character (not start of string), preserve it
const prefix = match[0] !== 'h' && match[0] !== 'w' ? match[0] : '';
const actualUrl = prefix ? match.slice(1) : match;
const href = actualUrl.startsWith('www.') ? 'http://' + actualUrl : actualUrl;
return prefix + '<a href="' + href + '" target="_blank" rel="noopener noreferrer" style="color:#00a8ff;text-decoration:underline">' + actualUrl + '</a>';
});
}
function stripIRC(text) {
@@ -596,17 +615,8 @@
addMessage('Status', chatNick('***', '#888') + formatIRC(p[p.length - 1] || ''), timestamp);
// Auto-join channels after end of MOTD
setTimeout(function () {
if (registered) {
send('JOIN ' + AUTO_JOIN.join(','));
// Focus the designated channel once it's joined
const waitForFocus = setInterval(function () {
if (windows[AUTO_JOIN_FOCUS]) {
switchWindow(AUTO_JOIN_FOCUS);
clearInterval(waitForFocus);
}
}, 200);
// Safety: stop waiting after 15 seconds
setTimeout(function () { clearInterval(waitForFocus); }, 15000);
if (registered && autoJoinChannels.length) {
send('JOIN ' + autoJoinChannels.join(','));
}
}, 3000);
break;
@@ -670,7 +680,8 @@
case 'JOIN': {
const chan = p[0].split(' ')[0];
if (fromNick === nick && chan.toLowerCase() === BLACKHOLE) {
// Auto-part #blackhole only on SuperNETs network
if (fromNick === nick && chan.toLowerCase() === BLACKHOLE && serverHost.toLowerCase().includes('supernets')) {
send('PART ' + chan);
break;
}
@@ -941,15 +952,18 @@
createWindow('Status');
createWindow('Hilights');
switchWindow('Status');
addMessage('Status', chatNick('***', '#888') + '<span style="color:#888">Connecting to ' + esc(SERVER_URL) + ' ...</span>');
const protocol = useSSL ? 'wss://' : 'ws://';
const serverURL = protocol + serverHost + ':' + serverPort;
addMessage('Status', chatNick('***', '#888') + '<span style="color:#888">Connecting to ' + esc(serverURL) + ' ...</span>');
ws = new WebSocket(SERVER_URL);
ws = new WebSocket(serverURL);
ws.onopen = function () {
addMessage('Status', chatNick('***', '#0f0') + '<span style="color:#0f0">WebSocket connected, negotiating...</span>');
send('CAP LS 302');
send('NICK ' + nick);
send('USER webirc 0 * :https://webchat.supernets.org');
send('USER webchat 0 * :SuperChat IRC Gateway');
};
ws.onmessage = function (event) {
@@ -981,24 +995,84 @@
};
}
// ============================================================
// Cookie helpers
// ============================================================
function setCookie(name, value, days) {
const d = new Date();
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = name + '=' + encodeURIComponent(value) + ';expires=' + d.toUTCString() + ';path=/';
}
function getCookie(name) {
const nameEQ = name + '=';
const ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length));
}
return null;
}
function deleteCookie(name) {
document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/';
}
// ============================================================
// Login
// ============================================================
function doLogin() {
// Get form values
serverHost = loginServerEl.value.trim();
serverPort = parseInt(loginPortEl.value, 10);
useSSL = loginSSLEl.checked;
let n = loginNickEl.value.trim();
// Remove invalid chars: only alphanumeric, -, _, [, ]
n = n.replace(/[^a-zA-Z0-9_\-\[\]]/g, '');
// Limit to 20 chars
n = n.substring(0, 20);
const channelsInput = loginChannelsEl.value.trim();
// Validation: Can't start with a number
// Validate server
if (!serverHost) {
alert('Please enter a server address.');
return;
}
// Validate port
if (!serverPort || serverPort < 1 || serverPort > 65535) {
alert('Please enter a valid port (1-65535).');
return;
}
// Validate and clean nick
n = n.replace(/[^a-zA-Z0-9_\-\[\]]/g, '');
n = n.substring(0, 20);
if (n && /^[0-9]/.test(n)) {
alert('Nickname cannot start with a number. Please choose a different nickname.');
return;
}
if (!n) n = 'WebUser' + Math.floor(Math.random() * 99999);
nick = n;
// Parse channels
autoJoinChannels = channelsInput.split(',')
.map(function(ch) { return ch.trim(); })
.filter(function(ch) { return ch.length > 0; });
// Save to cookies if remember is checked
if (loginRememberEl.checked) {
setCookie('irc_server', serverHost, 365);
setCookie('irc_port', serverPort, 365);
setCookie('irc_ssl', useSSL ? '1' : '0', 365);
setCookie('irc_nick', nick, 365);
setCookie('irc_channels', channelsInput, 365);
} else {
// Clear cookies if unchecked
deleteCookie('irc_server');
deleteCookie('irc_port');
deleteCookie('irc_ssl');
deleteCookie('irc_nick');
deleteCookie('irc_channels');
}
loginEl.classList.add('hidden');
appEl.classList.remove('hidden');
requestNotificationPermission();
@@ -1006,31 +1080,42 @@
connect();
}
const params = new URLSearchParams(window.location.search);
const urlNick = params.get('nick');
if (urlNick && urlNick.trim()) {
let n = urlNick.trim();
n = n.replace(/[^a-zA-Z0-9_\-\[\]]/g, '');
n = n.substring(0, 20);
// Load saved settings from cookies
function loadSavedSettings() {
const savedServer = getCookie('irc_server');
const savedPort = getCookie('irc_port');
const savedSSL = getCookie('irc_ssl');
const savedNick = getCookie('irc_nick');
const savedChannels = getCookie('irc_channels');
// Skip if starts with number
if (n && /^[0-9]/.test(n)) {
// Don't auto-login with invalid nick from URL
} else {
nick = n;
if (nick) {
loginEl.classList.add('hidden');
appEl.classList.remove('hidden');
requestNotificationPermission();
connect();
}
if (savedServer) {
loginServerEl.value = savedServer;
loginRememberEl.checked = true;
}
if (savedPort) {
loginPortEl.value = savedPort;
}
if (savedSSL !== null) {
loginSSLEl.checked = savedSSL === '1';
}
if (savedNick) {
loginNickEl.value = savedNick;
}
if (savedChannels) {
loginChannelsEl.value = savedChannels;
}
}
// Load saved settings on page load
loadSavedSettings();
loginBtnEl.addEventListener('click', doLogin);
loginNickEl.addEventListener('keydown', function (e) {
if (e.key === 'Enter') doLogin();
});
loginChannelsEl.addEventListener('keydown', function (e) {
if (e.key === 'Enter') doLogin();
});
// ============================================================
// Toggle buttons

View File

@@ -49,8 +49,9 @@ body {
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
gap: 10px;
padding: 20px;
max-width: 320px;
}
#login-logo {
@@ -63,36 +64,91 @@ body {
#login-logo:hover {
filter: drop-shadow(0 0 12px rgba(0, 255, 255, 0.7)) drop-shadow(0 0 30px rgba(0, 136, 255, 0.4)) drop-shadow(0 0 50px rgba(0, 255, 255, 0.25));
cursor: pointer;
}
#login-title-link {
text-decoration: none;
}
#login-title {
font-size: 26px;
font-size: 20px;
color: #fff;
letter-spacing: 6px;
letter-spacing: 3px;
text-transform: uppercase;
white-space: nowrap;
text-shadow:
0 0 7px rgba(0, 255, 255, 0.6),
0 0 14px rgba(0, 255, 255, 0.4),
0 0 28px rgba(0, 255, 255, 0.2),
0 0 56px rgba(0, 136, 255, 0.15);
transition: text-shadow 0.3s ease;
}
#login-nick {
#login-title:hover {
text-shadow:
0 0 10px rgba(0, 255, 255, 0.8),
0 0 20px rgba(0, 255, 255, 0.6),
0 0 40px rgba(0, 255, 255, 0.4),
0 0 80px rgba(0, 136, 255, 0.3);
cursor: pointer;
}
#login-server, #login-port, #login-nick, #login-channels {
background: #111;
color: #c0c0c0;
border: 1px solid #333;
padding: 10px 12px;
padding: 8px 10px;
font-family: 'Courier New', 'Lucida Console', monospace;
font-size: 16px;
font-size: 14px;
outline: none;
width: 260px;
text-align: center;
width: 280px;
}
#login-nick:focus {
#login-server:focus, #login-port:focus, #login-nick:focus, #login-channels:focus {
border-color: #666;
}
#login-row {
display: flex;
gap: 8px;
width: 280px;
align-items: center;
}
#login-port {
width: 80px;
flex-shrink: 0;
}
#login-ssl-label {
color: #888;
font-size: 13px;
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
gap: 4px;
}
#login-ssl {
cursor: pointer;
}
#login-remember-label {
color: #888;
font-size: 13px;
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
gap: 4px;
}
#login-remember {
cursor: pointer;
}
#login-btn {
background: #1a1a1a;
color: #888;
@@ -377,12 +433,20 @@ body {
}
#login-title {
font-size: 18px;
letter-spacing: 4px;
font-size: 16px;
letter-spacing: 2px;
}
#login-nick {
width: 220px;
#login-server, #login-port, #login-nick, #login-channels {
width: 260px;
}
#login-row {
width: 260px;
}
#login-port {
width: 70px;
}
#channels {

View File

@@ -3,17 +3,24 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>SuperNETs IRC</title>
<title>SuperChat IRC Gateway</title>
<link rel="icon" href="./include/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="./include/style.css">
</head>
<body>
<div id="login">
<div id="login-box">
<img id="login-logo" src="./include/logo.png" alt="SuperNETs">
<div id="login-title">SuperNETs IRC</div>
<input type="text" id="login-nick" placeholder="Enter nickname..." autofocus spellcheck="false" autocapitalize="off" autocomplete="off" maxlength="20">
<button id="login-btn">Enter The Void</button>
<a href="https://git.supernets.org/supernets/superchat" target="_blank" rel="noopener"><img id="login-logo" src="./include/logo.png" alt="SuperChat"></a>
<a href="https://git.supernets.org/supernets/superchat" target="_blank" rel="noopener" id="login-title-link"><div id="login-title">SuperChat IRC Gateway</div></a>
<input type="text" id="login-server" placeholder="Server" spellcheck="false" autocapitalize="off" autocomplete="off" value="irc.supernets.org">
<div id="login-row">
<input type="number" id="login-port" placeholder="Port" min="1" max="65535" value="6697">
<label id="login-ssl-label"><input type="checkbox" id="login-ssl" checked> SSL/TLS</label>
</div>
<input type="text" id="login-nick" placeholder="Nickname" autofocus spellcheck="false" autocapitalize="off" autocomplete="off" maxlength="20">
<input type="text" id="login-channels" placeholder="Channels (comma-separated)" spellcheck="false" autocapitalize="off" autocomplete="off" value="#dev,#comms,#exchange,#hardchats,#scroll,#superbowl">
<label id="login-remember-label"><input type="checkbox" id="login-remember"> Remember my settings</label>
<button id="login-btn">Connect</button>
</div>
</div>
<div id="app" class="hidden">