Upload code

This commit is contained in:
Heath123 2020-11-27 18:25:53 +01:00
commit 340082f454
45 changed files with 4816 additions and 0 deletions

181
pages/channel.js Normal file
View file

@ -0,0 +1,181 @@
var fs = require('fs');
var minify = require('html-minifier').minify;
var escape = require('escape-html');
var md = require('markdown-it')({breaks: true, linkify: true});
var he = require('he'); // Encodes HTML attributes
// Minify at runtime to save data on slow connections, but still allow editing the unminified file easily
// Is that a bad idea?
// Templates for viewing the messages in a channel
const channel_template = minify(fs.readFileSync('pages/templates/channel.html', 'utf-8'));
const message_template = minify(fs.readFileSync('pages/templates/message/message.html', 'utf-8'));
const first_message_content_template = minify(fs.readFileSync('pages/templates/message/first_message_content.html', 'utf-8'));
const merged_message_content_template = minify(fs.readFileSync('pages/templates/message/merged_message_content.html', 'utf-8'));
const mention_template = minify(fs.readFileSync('pages/templates/message/mention.html', 'utf-8'));
const input_template = minify(fs.readFileSync('pages/templates/channel/input.html', 'utf-8'));
const input_disabled_template = minify(fs.readFileSync('pages/templates/channel/input_disabled.html', 'utf-8'));
const no_message_history_template = minify(fs.readFileSync('pages/templates/channel/no_message_history.html', 'utf-8'));
function strReplace(string, needle, replacement) {
return string.split(needle).join(replacement||"");
};
// https://stackoverflow.com/questions/1967119/why-does-javascript-replace-only-first-instance-when-using-replace
exports.processChannel = async function processChannel(bot, req, res, args, discordID) {
try {
response = "";
chnl = await bot.client.channels.fetch(args[2]);
} catch (err) {
chnl = undefined;
}
if (chnl) {
member = await chnl.guild.members.fetch(discordID);
user = member.user;
username = user.tag;
if (member.displayName != user.username) {
username = member.displayName + " (@" + user.tag + ")";
}
if (!member.permissionsIn(chnl).has("VIEW_CHANNEL", true)) {
res.write("You don't have permission to do that!");
res.end();
return;
}
if (!member.permissionsIn(chnl).has("READ_MESSAGE_HISTORY", true)) {
template = strReplace(channel_template, "{$SERVER_ID}", chnl.guild.id)
template = strReplace(template, "{$CHANNEL_ID}", chnl.id)
if (member.permissionsIn(chnl).has("SEND_MESSAGES", true)) {
final = strReplace(template, "{$INPUT}", input_template);
} else {
final = strReplace(template, "{$INPUT}", input_disabled_template);
}
final = strReplace(final, "{$MESSAGES}", no_message_history_template); // You do not have permission... message
res.write(final); //write a response to the client
res.end(); //end the response
return;
}
console.log("Processed valid channel request");
messages = await bot.getHistoryCached(chnl);
lastauthor = undefined;
lastmember = undefined;
lastdate = new Date('1995-12-17T03:24:00');
currentmessage = "";
islastmessage = false;
handlemessage = async function (item) { // Save the function to use later in the for loop and to process the last message
if (lastauthor) { // Only consider the last message if this is not the first
// If the last message is not going to be merged with this one, put it into the response
if (islastmessage || lastauthor.id != item.author.id || lastauthor.username != item.author.username || item.createdAt - lastdate > 420000) {
currentmessage = message_template.replace("{$MESSAGE_CONTENT}", currentmessage);
if (lastmember) { // Webhooks are not members!
currentmessage = currentmessage.replace("{$MESSAGE_AUTHOR}", escape(lastmember.displayName));
} else {
currentmessage = currentmessage.replace("{$MESSAGE_AUTHOR}", escape(lastauthor.username));
}
var url = lastauthor.avatarURL();
if (lastauthor.avatarURL && url && url.toString().startsWith("http")) { // Sometimes the URL is null or something else
currentmessage = currentmessage.replace("{$PROFILE_URL}", url);
}
currentmessage = strReplace(currentmessage, "{$MESSAGE_DATE}", lastdate.toLocaleTimeString('en-US') + " " + lastdate.toDateString());
currentmessage = strReplace(currentmessage, "{$TAG}", he.encode(JSON.stringify("@" + lastauthor.tag)));
response += currentmessage;
currentmessage = "";
}
}
if (!item) { // When processing the last message outside of the forEach item is undefined
return;
}
// messagetext = strReplace(escape(item.content), "\n", "<br>");
messagetext = /* strReplace( */ md.renderInline(item.content) /* , "\n", "<br>") */;
if (item.mentions) {
item.mentions.members.forEach(function(user) {
if (user) {
messagetext = strReplace(messagetext, "&lt;@" + user.id.toString() + "&gt;", mention_template.replace("{$USERNAME}", escape("@" + user.displayName)));
messagetext = strReplace(messagetext, "&lt;@!" + user.id.toString() + "&gt;", mention_template.replace("{$USERNAME}", escape("@" + user.displayName)));
}
});
}
// https://stackoverflow.com/questions/6323417/regex-to-extract-all-matches-from-string-using-regexp-exec
var regex = /&lt;#([0-9]{18})&gt;/g; // Regular expression to detect channel IDs
var m;
do {
m = regex.exec(messagetext);
if (m) {
try {
channel = await bot.client.channels.cache.get(m[1]);
} catch(err) {
console.log(err);
}
if (channel) {
messagetext = strReplace(messagetext, m[0], mention_template.replace("{$USERNAME}", escape("#" + channel.name)));
}
}
} while (m);
messagetext = strReplace(messagetext, "@everyone", mention_template.replace("{$USERNAME}", "@everyone"));
messagetext = strReplace(messagetext, "@here", mention_template.replace("{$USERNAME}", "@here"));
// If the last message is not going to be merged with this one, use the template for the first message, otherwise use the template for merged messages
if (!lastauthor || lastauthor.id != item.author.id || lastauthor.username != item.author.username || item.createdAt - lastdate > 420000) {
messagetext = first_message_content_template.replace("{$MESSAGE_TEXT}", messagetext);
} else {
messagetext = merged_message_content_template.replace("{$MESSAGE_TEXT}", messagetext);
}
lastauthor = item.author;
lastmember = item.member;
lastdate = item.createdAt;
currentmessage += messagetext;
}
for (const item of messages) {
await handlemessage(item);
}
// Handle the last message
// Uses the function in the foreach from earlier
islastmessage = true;
await handlemessage();
template = strReplace(channel_template, "{$SERVER_ID}", chnl.guild.id)
template = strReplace(template, "{$CHANNEL_ID}", chnl.id)
template = strReplace(template, "{$REFRESH_URL}", chnl.id + "?random=" + Math.random() + "#end")
if (member.permissionsIn(chnl).has("SEND_MESSAGES", true)) {
final = strReplace(template, "{$INPUT}", input_template);
} else {
final = strReplace(template, "{$INPUT}", input_disabled_template);
}
final = strReplace(final, "{$MESSAGES}", response);
res.writeHead(200, { "Content-Type": "text/html" });
res.write(final); //write a response to the client
res.end(); //end the response
} else {
res.writeHead(404, { "Content-Type": "text/html" });
res.write("Invalid channel!"); //write a response to the client
res.end(); //end the response
}
}

25
pages/forgot.js Normal file
View file

@ -0,0 +1,25 @@
var url = require('url');
var fs = require('fs');
var minify = require('html-minifier').minify;
var escape = require('escape-html');
var auth = require('../authentication.js');
const forgot_template = minify(fs.readFileSync('pages/templates/forgot.html', 'utf-8'));
const error_template = minify(fs.readFileSync('pages/templates/login/error.html', 'utf-8'));
function strReplace(string, needle, replacement) {
return string.split(needle).join(replacement||"");
};
exports.processForgot = async function(bot, req, res, args) {
parsedurl = url.parse(req.url, true);
response = forgot_template;
if (parsedurl.query.errortext) {
response = strReplace(response, "{$ERROR}", strReplace(error_template, "{$ERROR_MESSAGE}", strReplace(escape(parsedurl.query.errortext), "\n", "<br>")));
} else {
response = strReplace(response, "{$ERROR}", "");
}
res.write(response);
res.end();
}

37
pages/guest.js Normal file
View file

@ -0,0 +1,37 @@
var url = require('url');
var fs = require('fs');
var minify = require('html-minifier').minify;
var escape = require('escape-html');
var auth = require('../authentication.js');
const guest_login_template = minify(fs.readFileSync('pages/templates/guest.html', 'utf-8'));
const error_template = minify(fs.readFileSync('pages/templates/login/error.html', 'utf-8'));
function strReplace(string, needle, replacement) {
return string.split(needle).join(replacement||"");
};
exports.processGuestLogin = async function(bot, req, res, args) {
discordID = await auth.checkAuth(req, res, true); // true means that the user isn't redirected to the login page
if (discordID) {
// res.writeHead(200, {'Content-Type': 'text/html'});
res.writeHead(303, {"Location": "/server/", "Content-Type": "text/html"});
res.write('Logged in! Click <a href="/server/">here</a> to continue.');
} else {
parsedurl = url.parse(req.url, true);
response = guest_login_template;
if (parsedurl.query.redirect) {
response = strReplace(response, "{$REDIRECT_URL}", strReplace(parsedurl.query.redirect, '"', "%22"));
} else {
response = strReplace(response, "{$REDIRECT_URL}", "/server/");
}
if (parsedurl.query.errortext) {
response = strReplace(response, "{$ERROR}", strReplace(error_template, "{$ERROR_MESSAGE}", strReplace(escape(parsedurl.query.errortext), "\n", "<br>")));
} else {
response = strReplace(response, "{$ERROR}", "");
}
res.write(response);
}
res.end();
}

27
pages/index.js Normal file
View file

@ -0,0 +1,27 @@
var fs = require('fs');
var minify = require('html-minifier').minify;
var escape = require('escape-html');
var auth = require('../authentication.js');
const index_template = minify(fs.readFileSync('pages/templates/index.html', 'utf-8'));
const logged_in_template = minify(fs.readFileSync('pages/templates/index/logged_in.html', 'utf-8'));
const logged_out_template = minify(fs.readFileSync('pages/templates/index/logged_out.html', 'utf-8'));
function strReplace(string, needle, replacement) {
return string.split(needle).join(replacement||"");
};
exports.processIndex = async function(bot, req, res, args) {
discordID = await auth.checkAuth(req, res, true); // true means that the user isn't redirected to the login page
if (discordID) {
response = strReplace(index_template, "{$MENU_OPTIONS}",
strReplace(logged_in_template, "{$USER}", escape(await auth.getUsername(discordID)))
);
} else {
response = strReplace(index_template, "{$MENU_OPTIONS}", logged_out_template);
}
res.write(response);
res.end();
}

37
pages/login.js Normal file
View file

@ -0,0 +1,37 @@
var url = require('url');
var fs = require('fs');
var minify = require('html-minifier').minify;
var escape = require('escape-html');
var auth = require('../authentication.js');
const login_template = minify(fs.readFileSync('pages/templates/login.html', 'utf-8'));
const error_template = minify(fs.readFileSync('pages/templates/login/error.html', 'utf-8'));
function strReplace(string, needle, replacement) {
return string.split(needle).join(replacement||"");
};
exports.processLogin = async function(bot, req, res, args) {
discordID = await auth.checkAuth(req, res, true); // true means that the user isn't redirected to the login page
if (discordID) {
// res.writeHead(200, {'Content-Type': 'text/html'});
res.writeHead(303, {"Location": "/server/", "Content-Type": "text/html"});
res.write('Logged in! Click <a href="/server/">here</a> to continue.');
} else {
parsedurl = url.parse(req.url, true);
response = login_template;
if (parsedurl.query.redirect) {
response = strReplace(response, "{$REDIRECT_URL}", strReplace(parsedurl.query.redirect, '"', "%22"));
} else {
response = strReplace(response, "{$REDIRECT_URL}", "/server/");
}
if (parsedurl.query.errortext) {
response = strReplace(response, "{$ERROR}", strReplace(error_template, "{$ERROR_MESSAGE}", strReplace(escape(parsedurl.query.errortext), "\n", "<br>")));
} else {
response = strReplace(response, "{$ERROR}", "");
}
res.write(response);
}
res.end();
}

31
pages/register.js Normal file
View file

@ -0,0 +1,31 @@
var url = require('url');
var fs = require('fs');
var minify = require('html-minifier').minify;
var escape = require('escape-html');
var auth = require('../authentication.js');
const register_template = minify(fs.readFileSync('pages/templates/register.html', 'utf-8'));
const error_template = minify(fs.readFileSync('pages/templates/login/error.html', 'utf-8'));
function strReplace(string, needle, replacement) {
return string.split(needle).join(replacement||"");
};
exports.processRegister = async function(bot, req, res, args) {
discordID = await auth.checkAuth(req, res, true); // true means that the user isn't redirected to the login page
if (discordID) {
res.writeHead(302, {"Location": "/server/"});
res.write('Logged in! Click <a href="/server/">here</a> to continue.');
} else {
parsedurl = url.parse(req.url, true);
response = register_template;
if (parsedurl.query.errortext) {
response = strReplace(response, "{$ERROR}", strReplace(error_template, "{$ERROR_MESSAGE}", strReplace(escape(parsedurl.query.errortext), "\n", "<br>")));
} else {
response = strReplace(response, "{$ERROR}", "");
}
res.write(response);
}
res.end();
}

100
pages/send.js Normal file
View file

@ -0,0 +1,100 @@
const url = require('url');
const auth = require('../authentication.js');
const bot = require('../bot.js');
const discord = require('discord.js');
const send_special = require('../secrets/send_special.js'); // A secret function that proxies the webhook for security. Does nothing in the public version.
function strReplace(string, needle, replacement) {
return string.split(needle).join(replacement||"");
};
// https://stackoverflow.com/questions/1967119/why-does-javascript-replace-only-first-instance-when-using-replace
async function clean(server, nodelete) {
(await server.fetchWebhooks()).forEach(async function(item) {
if ((item.owner.username.search("Discross") != -1) && (item.id != nodelete)) {
try {
await item.delete();
} catch(err) {}
}
});
}
exports.sendMessage = async function sendMessage(bot, req, res, args, discordID) {
parsedurl = url.parse(req.url, true);
if (parsedurl.query.message != "") {
channel = await bot.client.channels.fetch(parsedurl.query.channel);
member = await channel.guild.members.fetch(discordID);
user = member.user;
username = user.tag;
if (member.displayName != user.username) {
username = member.displayName + " (@" + user.tag + ")";
}
// if (user.permissionsIn(channel).FLAGS)]
if (!member.permissionsIn(channel).has("SEND_MESSAGES", true)) { // True always allows admins to send messages
res.write("You don't have permission to do that!");
res.end();
return;
}
if (channel.guild.id == 421771267100901377) { // A secret function that proxies the webhook for security. Only for The Wii Hacking House because I have to hardcode the webhook URLs and that's the most likely target. I have a thing that automatically shuts the bot down if any server gets attacked anyway.
await send_special.send(channel, parsedurl.query.message, {username: username, avatarURL: user.avatarURL(), disableEveryone: true});
res.writeHead(302, {"Location": "/channels/" + parsedurl.query.channel + "#end"});
res.end();
return;
}
// webhooks (serverID INT, webhookID INT, url STRING)
webhookDB = await auth.dbQuerySingle("SELECT * FROM webhooks WHERE serverID=?", [channel.guild.id]);
if (!webhookDB) {
webhook = await channel.createWebhook("Discross", "pages/static/resources/logo.png", "Discross uses webhooks to send messages");
auth.dbQuerySingle("INSERT INTO webhooks VALUES (?,?,?)", [channel.guild.id, webhook.id, webhook.token]);
clean(channel.guild, webhook.id); // Clean up all webhooks except the new one
} else {
// webhook = new Discord.WebhookClient(webhookDB.webhookID, webhookDB.token);
try {
webhook = await bot.client.fetchWebhook(webhookDB.webhookID);
} catch (err) {
webhook = await channel.createWebhook("Discross", "pages/static/resources/logo.png", "Discross uses webhooks to send messages");
auth.dbQuerySingle("INSERT INTO webhooks VALUES (?,?,?)", [channel.guild.id, webhook.id, webhook.token]);
clean(channel.guild, webhook.id); // Clean up all webhooks except the new one
}
clean(channel.guild, webhookDB.webhookID);
}
processedmessage = parsedurl.query.message;
// https://stackoverflow.com/questions/6323417/regex-to-extract-all-matches-from-string-using-regexp-exec
// regex modified from https://www.reddit.com/r/discordapp/comments/6k4fml/username_requirements/
var regex = /@([^#]{2,32}#\d{4})/g; // Regular expression to detect user mentions
var m;
do {
m = regex.exec(processedmessage);
if (m) {
mentioneduser = await channel.guild.members.cache.find(member => member.user.tag == m[1]);
if (!mentioneduser) {
mentioneduser = (await channel.guild.members.fetch()).find(member => member.user.tag == m[1]);
}
if (mentioneduser) {
processedmessage = strReplace(processedmessage, m[0], "<@" + mentioneduser.id + ">");
}
}
} while (m);
await webhook.edit({channel: channel});
message = await webhook.send(processedmessage, {username: username, avatarURL: user.avatarURL(), disableEveryone: true});
bot.addToCache(message);
}
res.writeHead(302, {"Location": "/channels/" + parsedurl.query.channel + "#end"});
res.end();
}

117
pages/server.js Normal file
View file

@ -0,0 +1,117 @@
var fs = require('fs');
var minify = require('html-minifier').minify;
var escape = require('escape-html');
// Minify at runtime to save data on slow connections, but still allow editing the unminified file easily
// Is that a bad idea?
// Templates for viewing the channels in a server
const server_template = minify(fs.readFileSync('pages/templates/server.html', 'utf-8'));
const text_channel_template = minify(fs.readFileSync('pages/templates/channellist/textchannel.html', 'utf-8'));
const category_channel_template = minify(fs.readFileSync('pages/templates/channellist/categorychannel.html', 'utf-8'));
const server_icon_template = minify(fs.readFileSync('pages/templates/server/server_icon.html', 'utf-8'));
const invalid_server_template = minify(fs.readFileSync('pages/templates/server/invalid_server.html', 'utf-8'));
const cachedMembers = {}; // TODO: Find a better way
function strReplace(string, needle, replacement) {
return string.split(needle).join(replacement||"");
};
// https://stackoverflow.com/questions/1967119/why-does-javascript-replace-only-first-instance-when-using-replace
exports.processServer = async function (bot, req, res, args, discordID) {
guestServers = ["439461201731387392"];
guestChannels = ["608958017366654986"];
var isGuest = false;
if (typeof(discordID) == "object") {
isGuest = true;
}
serverList = "";
for (var server of bot.client.guilds.cache) {
server = server[1];
if (cachedMembers[discordID] && cachedMembers[discordID][server.id] !== undefined) {
member = cachedMembers[discordID][server.id];
} else if (!(isGuest && guestServers.includes(server.id))) {
try {
member = await server.members.fetch(discordID);
} catch (err) {
member = null;
}
if (!cachedMembers[discordID]) {
cachedMembers[discordID] = {};
}
cachedMembers[discordID][server.id] = member;
}
if ((isGuest && guestServers.includes(server.id)) || (member && member.user)) {
serverHTML = strReplace(server_icon_template, "{$SERVER_ICON_URL}", server.iconURL());
serverHTML = strReplace(serverHTML, "{$SERVER_URL}", "./" + server.id);
serverList += serverHTML;
}
}
response = server_template.replace("{$SERVER_LIST}", serverList);
server = bot.client.guilds.cache.get(args[2]);
try {
if (!(isGuest && guestServers.includes(server.id))) {
member = await server.members.fetch(discordID);
user = member.user;
username = user.tag;
if (member.displayName != user.username) {
username = member.displayName + " (@" + user.tag + ")";
}
}
//} else {
// username = "Guest";
//}
// username =
if (!((isGuest && guestServers.includes(server.id)) || member.user)) {
server = undefined;
}
} catch (err) { // If they aren't in the server
// console.log(err); TODO: Only ignore TypeError: Cannot read property 'members' of undefined
server = undefined; // Act like it doesn't exist
}
if (server) {
categories = server.channels.cache.filter(channel => channel.type == "category");
categoriesSorted = categories.sort((a, b) => (a.calculatedPosition - b.calculatedPosition));
channelsSorted = server.channels.cache.filter(channel => channel.type != "category" && channel.type != "voice" && !channel.parent).array(); // Start with lone channels (no category)
channelsSorted = channelsSorted.sort((a, b) => (a.calculatedPosition - b.calculatedPosition));
categoriesSorted.forEach(function(category) {
channelsSorted.push(category);
channelsSorted = channelsSorted.concat(
category.children.sort((a, b) => (a.calculatedPosition - b.calculatedPosition))
.array()
.filter(channel => channel.type != "voice")
);
});
channelList = "";
channelsSorted.forEach(function(item) {
if ((isGuest && guestChannels.includes(item.id)) || (member.permissionsIn && member.permissionsIn(item).has("VIEW_CHANNEL", true))) {
if (item.type == "category") {
channelList += category_channel_template.replace("{$CHANNEL_NAME}", escape(item.name));
} else {
channelList += text_channel_template.replace("{$CHANNEL_NAME}", escape(item.name)).replace("{$CHANNEL_LINK}", "../channels/" + item.id + "#end");
}
}
});
} else {
channelList = invalid_server_template;
}
response = response.replace("{$CHANNEL_LIST}", channelList);
res.writeHead(200, { "Content-Type": "text/html" });
res.write(response);
res.end();
}

214
pages/static/connection.js Normal file
View file

@ -0,0 +1,214 @@
connectiontype = "none";
latest_message_id = 0;
messages = [];
isxhr = false;
authkey = "authpls";
// nocache
// https://stackoverflow.com/a/15339941
function Xhr(){ /* returns cross-browser XMLHttpRequest, or null if unable */
try {
return new XMLHttpRequest();
}catch(e){}
try {
return new ActiveXObject("Msxml3.XMLHTTP");
}catch(e){}
try {
return new ActiveXObject("Msxml2.XMLHTTP.6.0");
}catch(e){}
try {
return new ActiveXObject("Msxml2.XMLHTTP.3.0");
}catch(e){}
try {
return new ActiveXObject("Msxml2.XMLHTTP");
}catch(e){}
try {
return new ActiveXObject("Microsoft.XMLHTTP");
}catch(e){}
return null;
}
function addMessage(text) {
messages = messages.concat(text);
// console.log(text);
// console.log(messages);
// var node = document.createElement("div"); // Create a <li> node
// var textnode = document.createTextNode(text); // Create a text node
// node.appendChild(textnode); // Append the text to <li>
// document.getElementById("myList").appendChild(node); // Append <li> to <ul> with id="myList"
// ws.close();
document.getElementById("myList").innerHTML = messages.join("<br>");
}
function addLongpoll(id) {
addScript("/longpoll.js?" + id, 'longpollScript');
}
function addScript(src, elementID) {
if (isxhr) {
xhttp2.open("GET", src, true);
xhttp2.send(null);
} else {
document.getElementById(elementID).innerHTML = "";
var script = document.createElement('script');
script.setAttribute('src', src);
document.getElementById(elementID).appendChild(script);
}
}
function auth() {
if (connectiontype == "websocket") {
send("AUTH " + authkey);
}
}
function send(message) {
if (connectiontype == "none") {
} else if (connectiontype == "websocket") {
ws.send(message);
} else if (connectiontype == "longpoll") {
time = (new Date()).getTime().toString();
// alert(encodeURIComponent("test"));
// alert(encodeURIComponent(message));
addScript('/api.js?uid=' +
time +
'&message=' +
message
+ '&authkey=' +
authkey,
'apiScript');
}
}
/* document.getElementById('messagebox').onkeypress = function(e){
if (!e) e = window.event;
var keyCode = e.keyCode || e.which;
if (keyCode == '13' && document.getElementById('messagebox').value != ""){
// alert("s");
// Enter pressed
send("SEND " + document.getElementById('messagebox').value);
document.getElementById('messagebox').value = "";
return false;
}
} */
function myFunction(e) {
if (e.preventDefault) {
e.preventDefault();
}
send("SEND " + document.getElementById("messagebox").value);
document.getElementById("messagebox").value = "";
return false;
}
function longpoll_xhr(id) {
xhttp.open("GET", "/longpoll-xhr?" + id + "&uid=" + (new Date()).getTime().toString(), true);
xhttp.send(null);
}
function setup_xhr() {
xhttp = Xhr();
xhttp.onreadystatechange = function() {
// alert("test " + xhttp.responseText);
if (xhttp.readyState == 4) {
// alert(xhttp.status);
// alert(xhttp.responseText);
eval(xhttp.responseText);
// addMessage(JSON.parse(this.responseText));
setup_xhr();
// longpoll_xhr(latest_message_id);
}
}
xhttp.open("GET", "/longpoll-xhr?" + latest_message_id, true);
xhttp.send(null);
}
xhttp2 = Xhr();
// function WebSocketTest(usews) {
if (window.WebSocket || window.MozWebSocket) {
if (!window.WebSocket) {
window.WebSocket = window.MozWebSocket;
}
connectiontype = "websocket";
// Let us open a web socket
ws = new WebSocket("wss://" + location.host + "/");
ws.onopen = function() {
auth();
// Web Socket is connected, send data using send()
// ws.send("Message to send");
// alert("Message is sent...");
};
ws.onmessage = function (evt) {
var received_msg = evt.data;
addMessage(received_msg);
};
ws.onclose = function() {
// websocket is closed.
if (xhttp2) {
connectiontype = "longpoll";
// addLongpoll(latest_message_id);
isxhr = true;
setup_xhr();
longpoll_xhr(latest_message_id);
} else {
connectiontype = "longpoll";
isxhr = false
addLongpoll(latest_message_id);
// setup_xhr();
// longpoll_xhr(latest_message_id);
}
};
} else {
// The browser doesn't support WebSocket maybe
if (xhttp2) {
connectiontype = "longpoll";
// addLongpoll(latest_message_id);
isxhr = true;
setup_xhr();
longpoll_xhr(latest_message_id);
} else {
connectiontype = "longpoll";
isxhr = false
addLongpoll(latest_message_id);
// setup_xhr();
// longpoll_xhr(latest_message_id);
}
}
// }

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

36
pages/static/webapp.html Normal file
View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Discross</title>
<meta name="description" content="A work-in-progress unofficial Discord relay (that acts like a client) aiming to support all platforms that support HTML.">
<meta name="keywords" content="Discord,client,wii,nintendo switch,switch">
<meta name="author" content="circuit10#0158">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- iOS web app support - https://gist.github.com/tfausak/2222823n !-->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="Discross">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="viewport" content="initial-scale=1,minimum-scale=1,maximum-scale=1">
<meta name="format-detection" content="telephone=no">
<link rel="apple-touch-icon" href="resources/logo.png">
<script>(function(a,b,c){if(c in b&&b[c]){var d,e=a.location,f=/^(a|html)$/i;a.addEventListener("click",function(a){d=a.target;while(!f.test(d.nodeName))d=d.parentNode;"href"in d&&(d.href.indexOf("http")||~d.href.indexOf(e.host))&&(a.preventDefault(),e.href=d.href)},!1)}})(document,window.navigator,"standalone")</script>
<style>
html, body {
margin: 0;
width: 100%;
height: 100%
}
iframe {
border: none;
width: 100%;
height: 100%
}
</style>
</head>
<body bgcolor="#303338" style="margin: 0;">
<iframe src="index.html">
</body>
</html>

View file

@ -0,0 +1,103 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Discross - #general</title>
<style>
body {
background: #303338;
}
br.nodisp {
display: none
}
th {
vertical-align: top;
position: relative;
top: 4px;
text-align: left;
font-weight: 400!important
}
th.join {
vertical-align: middle
}
hr {
border-top: none;
margin-bottom: 15px;
border-bottom: solid 1px #393c40;
border-left: solid 1px #393c40;
border-right: solid 1px #393c40
}
table {
padding-bottom: 5px
}
form table {
height: 86px;
background: #303338;
width: 100%;
padding: 15px 0 0 8px;
border-top: solid 1px #393c40
}
a {
color: #647dcd;
}
</style>
</head>
<body bgcolor="#303338">
<table>
<tbody>
<tr>
<td valign="top" style="vertical-align: top;">
<div class="links">
<a href="../server/{$SERVER_ID}"><img src="/resources/images/menu.png" alt="Menu" style="position: fixed; top: 32px;left: 12px;"></a>
<br><img src="/resources/images/menuspacer.png" alt=" " height="14px" width="32px"></div>
<br>
</td>
<td>
<div id="msgcontainer">
{$MESSAGES}
</div>
<div id = "myList"></div> <!-- TODO: Remove -->
<br>
<a id="end" name="end"></a>
<form action="../send">
<table>
<tbody>
<tr>
<td valign="middle" width="23px" style="vertical-align: middle; width: 23px;">
<a href="../server/{$SERVER_ID}"><img src="/resources/images/menu.png" alt="Menu" style="position: fixed; top: 32px;left: 12px;"></a>
</td>
<td>
<input type="hidden" id="channel" name="channel" value="{$CHANNEL_ID}">
<input type="hidden" id="lite" name="lite" value="False">
<div style="box-sizing: border-box;-moz-box-sizing: border-box;-webkit-box-sizing: border-box;padding: 16px;background: #393c40;width: 100%;border-radius: 10px;">
{$INPUT}
</div>
</td>
<td style="width: 55px;">
<input type="submit" style="width: 70px;right: 17px;position: relative;border-radius: 10px;border: transparent;color: #dddddd;height: 50px;background: #393c40;" value="Send">
</td>
<td valign="middle" style="vertical-align: middle; width: 48px;">
<a href="{$REFRESH_URL}"><img src="/resources/images/refresh.png" alt="Refresh"></a>
</td>
</tr>
</tbody>
</table>
</form>
</td>
</tr>
</tbody>
</table>
<script src="/connection.js"></script>
</body>
</html>

View file

@ -0,0 +1 @@
<input type="text" name="message" id="message" value="" style="color: #dddddd;background: #393c40;border: none;width: 100%;outline: none;box-shadow: 0 0 0 50px #393c40 inset;-webkit-text-fill-color: #dddddd;" required="" placeholder="Message #general">

View file

@ -0,0 +1 @@
<input type="text" name="message" id="message" value="" style="color: #dddddd;background: #393c40;border: none;width: 100%;outline: none;box-shadow: 0 0 0 50px #393c40 inset;-webkit-text-fill-color: #dddddd;" required="true" disabled="true" placeholder='You do not have permission to send messages in this channel.'>

View file

@ -0,0 +1,7 @@
<br>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;
You do not have permission to view the message history of this channel.
</font>
<br>
<br>

View file

@ -0,0 +1,4 @@
<font size="1" color="#999999" face="Arial, Helvetica, sans-serif">
{$CHANNEL_NAME}
</font>
<br>

View file

@ -0,0 +1,6 @@
<a href="{$CHANNEL_LINK}" style="text-decoration none;">
<font color="#999999" size="4" face="Arial, Helvetica, sans-serif">
#&nbsp;&nbsp;{$CHANNEL_NAME}
</font>
</a>
<br>

View file

@ -0,0 +1,47 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Discross - Forgot username or password</title>
</head>
<body style="margin: 0;" bgcolor="#303338">
<table>
<tbody>
<tr>
<td><a href="index.html"><img src="resources/logo.png" alt="Logo"></a></td>
<td>&nbsp;&nbsp;</td>
<td><font size="7" face="Arial, Helvetica, sans-serif" color="#dddddd">DISCROSS</font></td>
<td width="100%" align="right"><font size="4" face="Arial, Helvetica, sans-serif" color="#dddddd">&nbsp;&nbsp;<a style="color: #dddddd;" href="login.html"><font color="#dddddd">Login</font></a>&nbsp;&nbsp;</font><font size="4" face="Arial, Helvetica, sans-serif" color="#dddddd">&nbsp;&nbsp;<a style="color: #dddddd;" href="register.html"><font color="#dddddd">Register</font></a>&nbsp;&nbsp;</font></td>
</tr>
</tbody>
</table>
<hr style="margin: 0; position: relative; bottom: 4px;">
<br>
<table>
<tbody>
<tr>
<td>
&nbsp;&nbsp;&nbsp;&nbsp;
</td>
<td>
<form action="/forgot" method="post">
<font size="6" face="Arial, Helvetica, sans-serif" color="#dddddd">Forgot username or password<br>
WARNING:<br>
This will delete your account so that you can create a new one.</font>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">
<br><br>
Verification code:
<br><br>
Type ^connect on a server with Discross to get this.
<br><br>
<div style="padding: 16px;background: #393c40;width: 300px;border-radius: 10px;"><input type="text" name="token" id="token" value="" style="color: #dddddd;background: #393c40;border: none;width: 100%;outline: none;box-shadow: 0 0 0 50px #393c40 inset;-webkit-text-fill-color: #dddddd;" placeholder="Verification code"></div>
<br>{$ERROR}<br>
<input type="submit" style="border-radius: 10px;color: #dddddd;height: 44px;width:150px;border: none;background: #393c40;" value="Delete account">
</font>
</form>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View file

@ -0,0 +1,55 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Discross - Guest Login</title>
</head>
<body style="margin: 0;" bgcolor="#303338">
<table>
<tbody>
<tr>
<td><a href="index.html"><img src="resources/logo.png" alt="Logo"></a></td>
<td>&nbsp;&nbsp;</td>
<td><font size="7" face="Arial, Helvetica, sans-serif" color="#dddddd">DISCROSS</font></td>
<td width="100%" align="right"><font size="4" face="Arial, Helvetica, sans-serif" color="#dddddd">&nbsp;&nbsp;<a style="color: #dddddd;" href="login.html"><font color="#dddddd">Login</font></a>&nbsp;&nbsp;</font><font size="4" face="Arial, Helvetica, sans-serif" color="#dddddd">&nbsp;&nbsp;<a style="color: #dddddd;" href="register.html"><font color="#dddddd">Register</font></a>&nbsp;&nbsp;</font></td>
</tr>
</tbody>
</table>
<hr style="margin: 0; position: relative; bottom: 4px;">
<br>
<table>
<tbody>
<tr>
<td>
&nbsp;&nbsp;&nbsp;&nbsp;
</td>
<td>
<form action="/guest" method="post">
<font size="6" face="Arial, Helvetica, sans-serif" color="#dddddd">Guest Login</font><br><br>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">
Choose a username:
<br><br>
<div style="padding: 16px;background: #393c40;width: 300px;border-radius: 10px;"><input type="text" name="username" id="username" value="" style="color: #dddddd;background: #393c40;border: none;width: 100%;outline: none;box-shadow: 0 0 0 50px #393c40 inset;-webkit-text-fill-color: #dddddd;" placeholder="Username"></div>
<br>
<a href="/forgot.html" style="text-decoration none;">
<font color="#999999" size="4" face="Arial, Helvetica, sans-serif">
Forgot username or password
</font>
</a>
<br>
<br>{$ERROR}<br>
<input type="hidden" id="redirect" name="redirect" value="{$REDIRECT_URL}">
<input type="submit" style="border-radius: 10px;color: #dddddd;height: 44px;width:150px;border: none;background: #393c40;" value="Login">
<br>
<br>
<a style="text-decoration none;" href="/login.html">
<font size="4" face="Arial, Helvetica, sans-serif" color="#999999">Normal login</font>
</a>
</font>
</form>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View file

@ -0,0 +1 @@
<a href="/server/{$SERVER_ID}"><img src="{$SERVER_ICON_URL}" alt="{$SERVER_NAME}" width="48px" height="48px"></a>

View file

@ -0,0 +1,86 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Discross - Home</title>
<meta name="description"
content="A work-in-progress unofficial Discord relay (that acts like a client) aiming to support all platforms that support HTML.">
<meta name="keywords" content="Discord,client,wii,nintendo switch,switch">
<meta name="author" content="circuit10#0158">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body bgcolor="#303338" style="margin: 0;">
<table>
<tr>
<td><img src="./resources/logo.png" alt="Logo"></td>
<td>
<font size="7" face="Arial, Helvetica, sans-serif" color="#dddddd">discross</font>
</td>
<td width="100%" align="right">
<font size="4" face="Arial, Helvetica, sans-serif" color="#dddddd">&#160;&#160;
{$MENU_OPTIONS}
</font>
</td>
</tr>
</table>
<hr style="margin: 0; position: relative; bottom: 4px;">
<br>
<table>
<tbody>
<tr>
<td>
&nbsp;&nbsp;&nbsp;&nbsp;
</td>
<td>
<a style="color: #647dcd;" href='https://discordapp.com/invite/pXdeUqb'>
<font color='#647dcd'>Join our Discord!</font>
</a>
<br>
<a style="color: #647dcd;"
href='https://discordapp.com/oauth2/authorize?client_id=596074053903581226&scope=bot&permissions=8'>
<font color='#647dcd'>Add Discross to your server!</font>
</a>
<br>
<a style="color: #647dcd;" href='https://github.com/Heath123/discross'>
<font color='#647dcd'><del>Source code</del></font>
</a>
<font color='#dddddd'>Not uploaded yet</font>
<br>
<br>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">By logging in or registering you agree to the use of
cookies</font>
<br>
<br>
<font size="6" face="Arial, Helvetica, sans-serif" color="#dddddd">What is Discross?</font>
<br>
<br>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">A work-in-progress unofficial Discord relay (that
acts like a client) aiming to support all platforms that support HTML. It uses webhooks and a bot so it
doesn't need your user token, which would allow anyone to control your account. However, this means it only
works on servers with the bot.
<br><br>
<font size="6" face="Arial, Helvetica, sans-serif" color="#dddddd">What platforms are supported?</font>
<br>
<br>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">Virtually anything, although you may not get all
features. Platforms that are confirmed to work, to some extent:</font>
</td>
</tr>
</table>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">
<ul>
<li>Nintendo Switch (requires a workaround to access the web browser)</li>
<li>Nintendo Wii</li>
<li>Windows 95/98</li>
</ul>
<font size="3" face="Arial, Helvetica, sans-serif" color="#aaaaaa">&#160;&#160;&#160;&#160;Discross version
1.2.0-node-dev</font>
<br>
</body>
</html>

View file

@ -0,0 +1,3 @@
<font color="#aaaaaa" size="2">Logged in as {$USER}</font>&#160;&#160;
<a style="color: #dddddd;" href="./server/"><font color="#dddddd">Server list</font></a>&#160;&#160;
<a style="color: #dddddd;" href="./logout"><font color="#dddddd">Logout</font></a>&#160;&#160;

View file

@ -0,0 +1,2 @@
<a style="color: #dddddd;" href="./login.html"><font color="#dddddd">Login</font></a>&#160;&#160;
<a style="color: #dddddd;" href="./register.html"><font color="#dddddd">Register</font></a>&#160;&#160;

View file

@ -0,0 +1,59 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Discross - Login</title>
</head>
<body style="margin: 0;" bgcolor="#303338">
<table>
<tbody>
<tr>
<td><a href="index.html"><img src="resources/logo.png" alt="Logo"></a></td>
<td>&nbsp;&nbsp;</td>
<td><font size="7" face="Arial, Helvetica, sans-serif" color="#dddddd">DISCROSS</font></td>
<td width="100%" align="right"><font size="4" face="Arial, Helvetica, sans-serif" color="#dddddd">&nbsp;&nbsp;<a style="color: #dddddd;" href="login.html"><font color="#dddddd">Login</font></a>&nbsp;&nbsp;</font><font size="4" face="Arial, Helvetica, sans-serif" color="#dddddd">&nbsp;&nbsp;<a style="color: #dddddd;" href="register.html"><font color="#dddddd">Register</font></a>&nbsp;&nbsp;</font></td>
</tr>
</tbody>
</table>
<hr style="margin: 0; position: relative; bottom: 4px;">
<br>
<table>
<tbody>
<tr>
<td>
&nbsp;&nbsp;&nbsp;&nbsp;
</td>
<td>
<form action="/login" method="post">
<font size="6" face="Arial, Helvetica, sans-serif" color="#dddddd">Login</font><br><br>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">
Username:
<br><br>
<div style="padding: 16px;background: #393c40;width: 300px;border-radius: 10px;"><input type="text" name="username" id="username" value="" style="color: #dddddd;background: #393c40;border: none;width: 100%;outline: none;box-shadow: 0 0 0 50px #393c40 inset;-webkit-text-fill-color: #dddddd;" placeholder="Username"></div>
<br>
Password:
<br><br>
<div style="padding: 16px;background: #393c40;width: 300px;border-radius: 10px;"><input type="password" name="password" id="password" value="" style="color: #dddddd;background: #393c40;border: none;width: 100%;outline: none;box-shadow: 0 0 0 50px #393c40 inset;-webkit-text-fill-color: #dddddd;" placeholder="Password"></div>
<br>
<a href="/forgot.html" style="text-decoration none;">
<font color="#999999" size="4" face="Arial, Helvetica, sans-serif">
Forgot username or password
</font>
</a>
<br>
<br>{$ERROR}<br>
<input type="hidden" id="redirect" name="redirect" value="{$REDIRECT_URL}">
<input type="submit" style="border-radius: 10px;color: #dddddd;height: 44px;width:150px;border: none;background: #393c40;" value="Login">
<!-- <br>
<br>
<a style="text-decoration none;" href="/guest.html">
<font size="4" face="Arial, Helvetica, sans-serif" color="#999999">Guest mode</font>
</a> !-->
</font>
</form>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View file

@ -0,0 +1 @@
<font color="#ff0000">{$ERROR_MESSAGE}</font><br>

View file

@ -0,0 +1 @@
<span id="647916312823070720" name="647916312823070720" class="firstmessage">{$MESSAGE_TEXT}</span>

View file

@ -0,0 +1 @@
<font color="#647dcd" style="background-color: rgba(100,125,205,0.1);">{$USERNAME}</font>

View file

@ -0,0 +1 @@
<span id="647916335015395338" name="647916335015395338" class="message"><br>{$MESSAGE_TEXT}</span></font>

View file

@ -0,0 +1,27 @@
<div class="message">
<hr noshade="">
<br class="nodisp">
<table>
<tbody>
<tr>
<td valign="top"><img class="avatar" src="{$PROFILE_URL}" width="40px" height="40px" style="border-radius: 99px;"> </td>
<td valign="top"><font color="#303338">- </font> </td>
<td valign="top" align="left">
<div class="content">
<div>
<font onclick="document.getElementById('message').value += {$TAG}" class="name" color="#ffffff" face="Arial, Helvetica, sans-serif">{$MESSAGE_AUTHOR}</font>
<font class="date" size="1" face="Arial, Helvetica, sans-serif" color="#888888">{$MESSAGE_DATE}</font><br>
</div>
<div class="messagecontent">
<font size="2" face="Arial, Helvetica, sans-serif" color="#dddddd">
{$MESSAGE_CONTENT}
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>

View file

@ -0,0 +1,77 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Discross - Register</title>
</head>
<body style="margin: 0;" bgcolor="#303338">
<table>
<tbody>
<tr>
<td><a href="index.html"><img src="resources/logo.png" alt="Logo"></a></td>
<td>&nbsp;&nbsp;</td>
<td><font size="7" face="Arial, Helvetica, sans-serif" color="#dddddd">DISCROSS</font></td>
<td width="100%" align="right"><font size="4" face="Arial, Helvetica, sans-serif" color="#dddddd">&nbsp;&nbsp;<a style="color: #dddddd;" href="login.html"><font color="#dddddd">Login</font></a>&nbsp;&nbsp;</font><font size="4" face="Arial, Helvetica, sans-serif" color="#dddddd">&nbsp;&nbsp;<a style="color: #dddddd;" href="register.html"><font color="#dddddd">Register</font></a>&nbsp;&nbsp;</font></td>
</tr>
</tbody>
</table>
<hr style="margin: 0; position: relative; bottom: 4px;">
<br>
<table>
<tbody>
<tr>
<td>
&nbsp;&nbsp;&nbsp;&nbsp;
</td>
<td>
<form action="/register" method="post">
<table>
<tbody>
<tr>
<td>
<font size="6" face="Arial, Helvetica, sans-serif" color="#dddddd">Register</font><br><br>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">
Username:
<br><br>
<div style="padding: 16px;background: #393c40;width: 300px;border-radius: 10px;"><input type="text" name="username" id="username" value="" style="color: #dddddd;background: #393c40;border: none;width: 100%;outline: none;box-shadow: 0 0 0 50px #393c40 inset;-webkit-text-fill-color: #dddddd;" placeholder="Username"></div>
<br><br>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">
Password:<br><br>(don't use your Discord password)
<br><br>
<div style="padding: 16px;background: #393c40;width: 300px;border-radius: 10px;"><input type="password" name="password" id="password" value="" style="color: #dddddd;background: #393c40;border: none;width: 100%;outline: none;box-shadow: 0 0 0 50px #393c40 inset;-webkit-text-fill-color: #dddddd;" placeholder="Password"></div>
<br>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">
Confirm password:
<br><br>
<div style="padding: 16px;background: #393c40;width: 300px;border-radius: 10px;"><input type="password" name="confirm" id="confirm" value="" style="color: #dddddd;background: #393c40;border: none;width: 100%;outline: none;box-shadow: 0 0 0 50px #393c40 inset;-webkit-text-fill-color: #dddddd;" placeholder="Confirm password"></div>
<br>{$ERROR}<br>
<input type="submit" style="border-radius: 10px;color: #dddddd;height: 44px;width:150px;border: none;background: #393c40;;" value="Register">
</font>
</font>
</font>
</td>
<td valign="top">
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
</td>
<td style="vetical-align: top;" valign="top">
<br><br><br>
<font face="Arial, Helvetica, sans-serif" color="#dddddd">
Verification code:
<br><br>
Type ^connect on a server with Discross to get this.
<br><br>
<div style="padding: 16px;background: #393c40;width: 300px;border-radius: 10px;"><input type="text" name="token" id="token" value="" style="color: #dddddd;background: #393c40;border: none;width: 100%;outline: none;box-shadow: 0 0 0 50px #393c40 inset;-webkit-text-fill-color: #dddddd;" placeholder="Verification code"></div>
</font>
</td>
</tr>
</tbody>
</table>
</form>
</td>
</tr>
</tbody>
</table>
</body>
</html>

View file

@ -0,0 +1,32 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Discross - Menu</title>
<style>
hr {
border: solid 1px #494c50;
}
</style>
</head>
<body bgcolor="#303338">
<table>
<tbody>
<tr>
<td valign="top" style="vertical-align: top;">
<a href="/server/"><img src="/resources/logo.png" alt="Home" style="border-radius: 999px;" width="48px" height="48px"></a><br>
<hr noshade>
{$SERVER_LIST}
</td>
<td><font color="#303338">- </font></td>
<td valign="top" style="vertical-align: top;">
{$CHANNEL_LIST}
</td>
</tr>
</tbody>
</table>
</body>
</html>

View file

@ -0,0 +1 @@
<font face="Arial, Helvetica, sans-serif" color="#dddddd">Click on a server! <a href="/"><font face="Arial, Helvetica, sans-serif" color="#dddddd">Home</font></a></font>

View file

@ -0,0 +1 @@
<a href="{$SERVER_URL}"><img height="48px" width="48px" src="{$SERVER_ICON_URL}" alt="{$SERVER_NAME}" style="border-radius: 999px;"></a><br><br>