This commit is contained in:
Piyush Mishra 2018-03-05 03:17:13 +05:30
parent 4777db7372
commit f494f0a959
231 changed files with 13884 additions and 0 deletions

16
README.md Normal file
View File

@ -0,0 +1,16 @@
## Local install
Project is created with netbeans. It would be better to use netbeans to build it.
## You'll need
* Netbeans
* Java
* Server (Tomcat)
* Postgresql
## Modifications you need to do before build
* 'urrsm.sng.Config' contains important configurations. You need to change them accordingly

71
build.xml Normal file
View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<!-- By default, only the Clean and Build commands use this build script. -->
<!-- Commands such as Run, Debug, and Test only use this build script if -->
<!-- the Compile on Save feature is turned off for the project. -->
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
<!-- in the project's Project Properties dialog box.-->
<project name="UChats" default="default" basedir=".">
<description>Builds, tests, and runs the project UChats.</description>
<import file="nbproject/build-impl.xml"/>
<!--
There exist several targets which are by default empty and which can be
used for execution of your tasks. These targets are usually executed
before and after some main targets. They are:
-pre-init: called before initialization of project properties
-post-init: called after initialization of project properties
-pre-compile: called before javac compilation
-post-compile: called after javac compilation
-pre-compile-single: called before javac compilation of single file
-post-compile-single: called after javac compilation of single file
-pre-compile-test: called before javac compilation of JUnit tests
-post-compile-test: called after javac compilation of JUnit tests
-pre-compile-test-single: called before javac compilation of single JUnit test
-post-compile-test-single: called after javac compilation of single JUunit test
-pre-dist: called before archive building
-post-dist: called after archive building
-post-clean: called after cleaning build products
-pre-run-deploy: called before deploying
-post-run-deploy: called after deploying
Example of pluging an obfuscator after the compilation could look like
<target name="-post-compile">
<obfuscate>
<fileset dir="${build.classes.dir}"/>
</obfuscate>
</target>
For list of available properties check the imported
nbproject/build-impl.xml file.
Other way how to customize the build is by overriding existing main targets.
The target of interest are:
init-macrodef-javac: defines macro for javac compilation
init-macrodef-junit: defines macro for junit execution
init-macrodef-debug: defines macro for class debugging
do-dist: archive building
run: execution of project
javadoc-build: javadoc generation
Example of overriding the target for project execution could look like
<target name="run" depends="<PROJNAME>-impl.jar">
<exec dir="bin" executable="launcher.exe">
<arg file="${dist.jar}"/>
</exec>
</target>
Notice that overridden target depends on jar target and not only on
compile target as regular run target does. Again, for list of available
properties which you can use check the target you are overriding in
nbproject/build-impl.xml file.
-->
</project>

BIN
lib/gson-2.8.2-sources.jar Normal file

Binary file not shown.

BIN
lib/gson-2.8.2.jar Normal file

Binary file not shown.

BIN
lib/postgresql-42.2.1.jar Normal file

Binary file not shown.

76
nbproject/ant-deploy.xml Normal file
View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
Copyright (c) 2006, 2016 Oracle and/or its affiliates. All rights reserved.
Oracle and Java are registered trademarks of Oracle and/or its affiliates.
Other names may be trademarks of their respective owners.
The contents of this file are subject to the terms of either the GNU
General Public License Version 2 only ("GPL") or the Common
Development and Distribution License("CDDL") (collectively, the
"License"). You may not use this file except in compliance with the
License. You can obtain a copy of the License at
http://www.netbeans.org/cddl-gplv2.html
or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
specific language governing permissions and limitations under the
License. When distributing the software, include this License Header
Notice in each file and include the License file at
nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
particular file as subject to the "Classpath" exception as provided
by Oracle in the GPL Version 2 section of the License file that
accompanied this code. If applicable, add the following below the
License Header, with the fields enclosed by brackets [] replaced by
your own identifying information:
"Portions Copyrighted [year] [name of copyright owner]"
If you wish your version of this file to be governed by only the CDDL
or only the GPL Version 2, indicate your decision by adding
"[Contributor] elects to include this software in this distribution
under the [CDDL or GPL Version 2] license." If you do not indicate a
single choice of license, a recipient has the option to distribute
your version of this file under either the CDDL, the GPL Version 2 or
to extend the choice of license to its licensees as provided above.
However, if you add GPL Version 2 code and therefore, elected the GPL
Version 2 license, then the option applies only if the new code is
made subject to such option by the copyright holder.
Contributor(s):
-->
<project default="-deploy-ant" basedir=".">
<target name="-init" if="deploy.ant.enabled">
<property file="${deploy.ant.properties.file}"/>
<tempfile property="temp.module.folder" prefix="tomcat" destdir="${java.io.tmpdir}"/>
<unwar src="${deploy.ant.archive}" dest="${temp.module.folder}">
<patternset includes="META-INF/context.xml"/>
</unwar>
<xmlproperty file="${temp.module.folder}/META-INF/context.xml"/>
<delete dir="${temp.module.folder}"/>
</target>
<target name="-check-credentials" if="deploy.ant.enabled" depends="-init">
<fail message="Tomcat password has to be passed as tomcat.password property.">
<condition>
<not>
<isset property="tomcat.password"/>
</not>
</condition>
</fail>
</target>
<target name="-deploy-ant" if="deploy.ant.enabled" depends="-init,-check-credentials">
<echo message="Deploying ${deploy.ant.archive} to ${Context(path)}"/>
<taskdef name="deploy" classname="org.apache.catalina.ant.DeployTask"
classpath="${tomcat.home}/server/lib/catalina-ant.jar"/>
<deploy url="${tomcat.url}/manager" username="${tomcat.username}"
password="${tomcat.password}" path="${Context(path)}"
war="${deploy.ant.archive}"/>
<property name="deploy.ant.client.url" value="${tomcat.url}${Context(path)}"/>
</target>
<target name="-undeploy-ant" if="deploy.ant.enabled" depends="-init,-check-credentials">
<echo message="Undeploying ${Context(path)}"/>
<taskdef name="undeploy" classname="org.apache.catalina.ant.UndeployTask"
classpath="${tomcat.home}/server/lib/catalina-ant.jar"/>
<undeploy url="${tomcat.url}/manager" username="${tomcat.username}"
password="${tomcat.password}" path="${Context(path)}"/>
</target>
</project>

1446
nbproject/build-impl.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
build.xml.data.CRC32=eee99f0d
build.xml.script.CRC32=f46ca8d5
build.xml.stylesheet.CRC32=651128d4@1.77.1.1
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
nbproject/build-impl.xml.data.CRC32=eee99f0d
nbproject/build-impl.xml.script.CRC32=755bcc5e
nbproject/build-impl.xml.stylesheet.CRC32=99ea4b56@1.77.1.1

View File

@ -0,0 +1,89 @@
annotation.processing.enabled=true
annotation.processing.enabled.in.editor=true
annotation.processing.processors.list=
annotation.processing.run.all.processors=true
annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
build.classes.dir=${build.web.dir}/WEB-INF/classes
build.classes.excludes=**/*.java,**/*.form
build.dir=build
build.generated.dir=${build.dir}/generated
build.generated.sources.dir=${build.dir}/generated-sources
build.test.classes.dir=${build.dir}/test/classes
build.test.results.dir=${build.dir}/test/results
build.web.dir=${build.dir}/web
build.web.excludes=${build.classes.excludes}
client.urlPart=
compile.jsps=false
conf.dir=${source.root}/conf
debug.classpath=${build.classes.dir}:${javac.classpath}
debug.test.classpath=\
${run.test.classpath}
display.browser=true
# Files to be excluded from distribution war
dist.archive.excludes=
dist.dir=dist
dist.ear.war=${dist.dir}/${war.ear.name}
dist.javadoc.dir=${dist.dir}/javadoc
dist.war=${dist.dir}/${war.name}
endorsed.classpath=\
${libs.javaee-endorsed-api-7.0.classpath}
excludes=
file.reference.gson-2.8.2.jar=lib/gson-2.8.2.jar
file.reference.postgresql-42.2.1.jar=lib/postgresql-42.2.1.jar
includes=**
j2ee.compile.on.save=true
j2ee.copy.static.files.on.save=true
j2ee.deploy.on.save=true
j2ee.platform=1.7-web
j2ee.platform.classpath=${j2ee.server.home}/lib/annotations-api.jar:${j2ee.server.home}/lib/catalina-ant.jar:${j2ee.server.home}/lib/catalina-ha.jar:${j2ee.server.home}/lib/catalina-storeconfig.jar:${j2ee.server.home}/lib/catalina-tribes.jar:${j2ee.server.home}/lib/catalina.jar:${j2ee.server.home}/lib/ecj-4.4.2.jar:${j2ee.server.home}/lib/el-api.jar:${j2ee.server.home}/lib/jasper-el.jar:${j2ee.server.home}/lib/jasper.jar:${j2ee.server.home}/lib/jsp-api.jar:${j2ee.server.home}/lib/servlet-api.jar:${j2ee.server.home}/lib/tomcat-api.jar:${j2ee.server.home}/lib/tomcat-coyote.jar:${j2ee.server.home}/lib/tomcat-dbcp.jar:${j2ee.server.home}/lib/tomcat-i18n-es.jar:${j2ee.server.home}/lib/tomcat-i18n-fr.jar:${j2ee.server.home}/lib/tomcat-i18n-ja.jar:${j2ee.server.home}/lib/tomcat-jdbc.jar:${j2ee.server.home}/lib/tomcat-jni.jar:${j2ee.server.home}/lib/tomcat-util-scan.jar:${j2ee.server.home}/lib/tomcat-util.jar:${j2ee.server.home}/lib/tomcat-websocket.jar:${j2ee.server.home}/lib/websocket-api.jar
j2ee.server.type=Tomcat
jar.compress=false
javac.classpath=\
${file.reference.postgresql-42.2.1.jar}:\
${file.reference.gson-2.8.2.jar}
# Space-separated list of extra javac options
javac.compilerargs=
javac.debug=true
javac.deprecation=false
javac.processorpath=\
${javac.classpath}
javac.source=1.8
javac.target=1.8
javac.test.classpath=\
${javac.classpath}:\
${build.classes.dir}
javac.test.processorpath=\
${javac.test.classpath}
javadoc.additionalparam=
javadoc.author=false
javadoc.encoding=${source.encoding}
javadoc.noindex=false
javadoc.nonavbar=false
javadoc.notree=false
javadoc.preview=true
javadoc.private=false
javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
lib.dir=${web.docbase.dir}/WEB-INF/lib
no.dependencies=false
persistence.xml.dir=${conf.dir}
platform.active=default_platform
resource.dir=setup
run.test.classpath=\
${javac.test.classpath}:\
${build.test.classes.dir}
# Space-separated list of JVM arguments used when running a class with a main method or a unit test
# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value):
runmain.jvmargs=
source.encoding=UTF-8
source.reference.gson-2.8.2.jar=lib/gson-2.8.2-sources.jar
source.root=src
src.dir=${source.root}/java
test.src.dir=test
war.content.additional=
war.ear.name=${war.name}
war.name=UChats.war
web.docbase.dir=web
webinf.dir=web/WEB-INF

27
nbproject/project.xml Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.web.project</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/web-project/3">
<name>UChats</name>
<minimum-ant-version>1.6.5</minimum-ant-version>
<web-module-libraries>
<library dirs="200">
<file>${file.reference.postgresql-42.2.1.jar}</file>
<path-in-war>WEB-INF/lib</path-in-war>
</library>
<library dirs="200">
<file>${file.reference.gson-2.8.2.jar}</file>
<path-in-war>WEB-INF/lib</path-in-war>
</library>
</web-module-libraries>
<web-module-additional-libraries/>
<source-roots>
<root id="src.dir" name="Source Packages"/>
</source-roots>
<test-roots>
<root id="test.src.dir" name="Test Packages"/>
</test-roots>
</data>
</configuration>
</project>

2
src/conf/MANIFEST.MF Normal file
View File

@ -0,0 +1,2 @@
Manifest-Version: 1.0

View File

@ -0,0 +1,42 @@
package urrsm.sng;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.net.URI;
/**
*
* @author piyush
*
* Configurations for UChat Server
*
*/
public class Config
{
public static String admin = ""; // name of admin
public static String password = "bawal"; // password of admin
public static String mods[] = {"4f26ae"}; // trips of mods
public static String salt = "My Salt Is This"; // salt for trips
public static String dburl = "jdbc:postgresql://localhost:5432/database"; // url to database
public static String dbusername = "user"; // database username
public static String dbpassword = "pass"; // database password
static
{
if(System.getenv("DATABASE_URL")!=null)
{
try
{
URI dbUri = new URI(System.getenv("DATABASE_URL"));
dbusername = dbUri.getUserInfo().split(":")[0];
dbpassword = dbUri.getUserInfo().split(":")[1];
dburl = "jdbc:postgresql://" + dbUri.getHost() + ':' + dbUri.getPort() + dbUri.getPath();
}
catch(Exception ex)
{
Logger.getLogger(Config.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}

View File

@ -0,0 +1,23 @@
package urrsm.sng;
import java.util.ArrayList;
/**
*
* @author piyush
*
* Structure of json send between user and server
* It is required because we are using Gson
*
*/
public class JsonStructure {
String cmd;
String nick;
String channel;
String text;
boolean admin;
boolean mod;
String trip;
String ip;
ArrayList<String> nicks;
}

View File

@ -0,0 +1,141 @@
package urrsm.sng;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author piyush
*
* Record Manager
*
*/
public class RecordManager {
long blocktime = 3600000;
public RecordManager() throws ClassNotFoundException, SQLException
{
Class.forName("org.postgresql.Driver");
String sql = "CREATE TABLE IF NOT EXISTS blocklist(\n" +
" ip TEXT NOT NULL,\n" +
" time REAL NOT NULL\n"+
");";
try(Connection con = this.connect(); Statement stmt = con.createStatement())
{
stmt.execute(sql);
}
catch(SQLException ex)
{
Logger.getLogger(RecordManager.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* Block an ip
* @param ip
*/
public void block(String ip)
{
String sql = "INSERT INTO blocklist VALUES ( ? , ? );";
try(Connection conn = this.connect(); PreparedStatement pstmt = conn.prepareStatement(sql))
{
pstmt.setString(1, ip);
pstmt.setLong(2, System.currentTimeMillis());
pstmt.executeUpdate();
}
catch(SQLException ex)
{
Logger.getLogger(RecordManager.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* unblock an ip
* @param ip
*/
public void unblock(String ip)
{
String sql = "DELETE FROM blocklist WHERE ip = ? ;";
try(Connection conn = this.connect(); PreparedStatement pstmt = conn.prepareStatement(sql))
{
pstmt.setString(1, ip);
pstmt.executeUpdate();
}
catch(SQLException ex)
{
Logger.getLogger(RecordManager.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* unblock those ip who have passed blocktime in blocklist
*/
public void unblockrun()
{
String sql = "DELETE FROM blocklist WHERE ? >= time + ?;";
try(Connection conn = this.connect(); PreparedStatement pstmt = conn.prepareStatement(sql))
{
pstmt.setLong(1, System.currentTimeMillis());
pstmt.setLong(2, blocktime);
pstmt.executeUpdate();
}
catch(SQLException ex)
{
Logger.getLogger(RecordManager.class.getName()).log(Level.SEVERE, null, ex);
}
}
/**
* Check if user is blocked
* @param ip
* @return
*/
public boolean isUserBlocked(String ip)
{
String sql = "SELECT * FROM blocklist WHERE ip = ? ;";
try(Connection conn = this.connect(); PreparedStatement pstmt = conn.prepareStatement(sql))
{
pstmt.setString(1, ip);
ResultSet res = pstmt.executeQuery();
return res.next();
}
catch(SQLException ex)
{
Logger.getLogger(RecordManager.class.getName()).log(Level.SEVERE, null, ex);
}
return false;
}
/**
* Connect database
* @return
*/
private Connection connect()
{
Connection con = null;
try
{
con = DriverManager.getConnection(Config.dburl, Config.dbusername, Config.dbpassword);
}
catch(SQLException ex)
{
Logger.getLogger(RecordManager.class.getName()).log(Level.SEVERE, null, ex);
}
return con;
}
}

View File

@ -0,0 +1,25 @@
package urrsm.sng;
/**
*
* @author piyush
*/
import javax.servlet.annotation.WebListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
@WebListener()
public class RequestListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent event) {
}
@Override
public void requestInitialized(ServletRequestEvent event) {
WebScocketEnd.ipaddress = event.getServletRequest().getRemoteAddr();
}
}

View File

@ -0,0 +1,29 @@
package urrsm.sng;
import javax.websocket.Session;
/**
*
* @author piyush
*/
public class USession
{
Session peer;
String nick = "";
String channel = "";
String trip = new String();
String ip = "";
public USession(Session peer, String nick, String channel, String trip, long lastping)
{
this.peer = peer;
this.nick = nick;
this.channel = channel;
this.trip = trip;
}
public USession(Session peer)
{
this.peer = peer;
}
}

View File

@ -0,0 +1,402 @@
package urrsm.sng;
import com.google.gson.Gson;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Timer;
import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
/**
*
* @author piyush
*
* Websocket Manager
*
*/
@ServerEndpoint("/uchatsserver")
public class WebScocketEnd {
public static ArrayList<USession> sessions = new ArrayList<USession>();
public static String ipaddress = null;
public RecordManager recordmanager;
public Gson gson = new Gson();
public WebScocketEnd() throws ClassNotFoundException, SQLException
{
recordmanager = new RecordManager();
Timer timer = new javax.swing.Timer(1000, new java.awt.event.ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
recordmanager.unblockrun();
}
});
timer.start();
}
@OnMessage
public String onMessage(String message, Session session) throws IOException, EncodeException, NoSuchAlgorithmException {
JsonStructure json = gson.fromJson(message, JsonStructure.class);
USession ses = getSessionByPeer(session);
if(recordmanager.isUserBlocked(ipaddress))
{
return null;
}
// If user do Ping
if(json.cmd.equals("ping"))
{
// Do nothing
}
// If user join
else if(json.cmd.equals("join"))
{
String trip = " ";
if(json.nick.split("#").length != 1)
{
trip = calcHash(json.nick.split("#")[1]);
}
if(json.nick.split("#")[0].toLowerCase().equals(Config.admin.toLowerCase()) &&
!trip.toLowerCase().equals(calcHash(Config.password).toLowerCase()))
{
JsonStructure msg = new JsonStructure();
msg.cmd = "warn";
msg.text = "***Impersonating Admin???***";
session.getBasicRemote().sendText(gson.toJson(msg));
return null;
}
if(!json.nick.split("#")[0].matches("[a-zA-Z0-9_]*") && !(json.nick.split("#")[0].length() < 25))
{
JsonStructure msg = new JsonStructure();
msg.cmd = "warn";
msg.text = "Nickname must consist of up to 24 letters, numbers, and underscores!";
session.getBasicRemote().sendText(gson.toJson(msg));
return null;
}
if(json.channel.trim().equals(""))
{
JsonStructure msg = new JsonStructure();
msg.cmd = "warn";
msg.text = "Channel must not be spaces";
session.getBasicRemote().sendText(gson.toJson(msg));
return null;
}
for(USession sess : sessions)
{
if(sess.channel.equals(json.channel) && sess.nick.equals(json.nick.split("#")[0]))
{
JsonStructure msg = new JsonStructure();
msg.cmd = "warn";
msg.text = "Nickname already taken!";
session.getBasicRemote().sendText(gson.toJson(msg));
return null;
}
}
ses.nick = json.nick.split("#")[0];
ses.channel = json.channel;
ses.trip = trip;
ses.ip = ipaddress;
JsonStructure cmdonline = new JsonStructure();
cmdonline.cmd = "onlineAdd";
cmdonline.nick = json.nick.split("#")[0];
boardcast(gson.toJson(cmdonline),json.channel);
ArrayList<String> nicks = new ArrayList<String>();
for(USession sess : sessions)
{
if(sess.channel.endsWith(json.channel)) nicks.add(sess.nick);
}
JsonStructure onlinelist = new JsonStructure();
onlinelist.cmd = "onlineSet";
onlinelist.nicks = nicks;
session.getBasicRemote().sendText(gson.toJson(onlinelist));
}
//user sends message
else if(json.cmd.equals("chat"))
{
JsonStructure chat = new JsonStructure();
chat.cmd = "chat";
chat.nick = ses.nick;
chat.text = json.text;
chat.trip = ses.trip;
if(isAdmin(ses)) chat.admin = true;
if(isMod(ses)) chat.mod = true;
boardcast(gson.toJson(chat), ses.channel);
}
//user invites user to another random channel
else if(json.cmd.equals("invite"))
{
for(USession sess : sessions)
{
if(sess.nick.equals(json.nick))
{
String channel = String.valueOf(calcHash(String.valueOf(Math.random()))).substring(0, 6);
if(sess.peer == ses.peer)
{
return null;
}
JsonStructure chat = new JsonStructure();
chat.cmd = "info";
chat.text = ses.nick + " invited you to ?"+channel;
sess.peer.getBasicRemote().sendText(gson.toJson(chat));
chat.text = "you invited "+ses.nick +" to ?"+channel;
ses.peer.getBasicRemote().sendText(gson.toJson(chat));
}
}
}
// Status
if(json.cmd.equals("stats"))
{
ArrayList<String> channels = new ArrayList<String>();
for(USession sess : sessions)
{
if(channels.indexOf(sess.channel) == -1)
{
channels.add(sess.channel);
}
}
JsonStructure chat = new JsonStructure();
chat.cmd = "info";
chat.text = " Users Connected : "+sessions.size()+"\n"
+ " Channels : "+channels.size();
boardcast(gson.toJson(chat), ses.channel);
}
// super power
// Ban a user
else if(json.cmd.equals("ban"))
{
if(!isAdmin(ses) && !isMod(ses))
{
return null;
}
for(USession sess : sessions)
{
if(ses.channel.equals(sess.channel) && json.nick.equals(sess.nick))
{
recordmanager.block(sess.ip);
JsonStructure chat = new JsonStructure();
chat.cmd = "chat";
chat.nick = "Server";
chat.text = "Ip "+sess.ip+" is banned!";
chat.trip = "server";
ses.peer.getBasicRemote().sendText(gson.toJson(chat));
}
}
}
// Unban a user
else if(json.cmd.equals("unban"))
{
if(!isAdmin(ses) && !isMod(ses))
{
return null;
}
recordmanager.unblock(json.ip);
JsonStructure chat = new JsonStructure();
chat.cmd = "chat";
chat.nick = "Server";
chat.text = "Ip "+json.ip+" is unbanned!";
chat.trip = "server";
ses.peer.getBasicRemote().sendText(gson.toJson(chat));
}
// VIP Super Power
// List users and their info
else if(json.cmd.equals("listUsers"))
{
if(!isAdmin(ses))
{
return null;
}
String data = "";
for(USession sess : sessions)
{
data = data + "\n"+sess.nick+"\t"+sess.channel+"\t"+sess.trip+"\t"+sess.ip;
}
JsonStructure chat = new JsonStructure();
chat.cmd = "chat";
chat.nick = "Server";
chat.text = "Nick\tChannel\tTrip\tIp\n"+data;
chat.trip = "server";
ses.peer.getBasicRemote().sendText(gson.toJson(chat));
}
// bordcast a message to all channel
else if(json.cmd.equals("broadcast"))
{
if(!isAdmin(ses))
{
return null;
}
JsonStructure chat = new JsonStructure();
chat.cmd = "chat";
chat.nick = "Server";
chat.text = json.text;
chat.trip = "server";
wildspread(gson.toJson(chat));
}
return null;
}
@OnOpen
public void onOpen (Session peer) throws IOException {
// Limit number of user and filter blocked users
if(sessions.size() < 10000 && !recordmanager.isUserBlocked(ipaddress))
{
sessions.add(new USession(peer, " ", " ", " ", System.currentTimeMillis()));
peer.getBasicRemote().sendText(ipaddress);
}
else
{
peer.getBasicRemote().sendText("Unable to book your seat in chatroom!");
}
}
@OnClose
public void onClose (Session peer) {
USession leavingsession = getSessionByPeer(peer);
if(!leavingsession.trip.equals(" "))
{
JsonStructure cmdonline = new JsonStructure();
cmdonline.cmd = "onlineRemove";
cmdonline.nick = leavingsession.nick;
String channel = leavingsession.channel;
sessions.remove(leavingsession);
boardcast(gson.toJson(cmdonline),channel);
}
else sessions.remove(leavingsession);
}
@OnError
public void onError(Throwable t) {
t.printStackTrace();
}
/**
* Get USession from Arraylist
* @param peer
* @return
*/
public USession getSessionByPeer(Session peer)
{
for(USession session : sessions)
{
if(session.peer == peer) return session;
}
return null;
}
/**
* Send message to a channel
* @param message
* @param channel
*/
public void boardcast(String message, String channel)
{
for(USession session : sessions)
{
try {
if(session.channel.equals(channel)) session.peer.getBasicRemote().sendText(message);
} catch (IOException ex) {
Logger.getLogger(WebScocketEnd.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
/**
* Send message to all channels
* @param message
*/
public void wildspread(String message)
{
for(USession session : sessions)
{
try {
session.peer.getBasicRemote().sendText(message);
} catch (IOException ex) {
Logger.getLogger(WebScocketEnd.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
/**
* Calculate SHA-1 sum with salt
* @param passwd
* @return
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
public String calcHash(String passwd) throws NoSuchAlgorithmException, UnsupportedEncodingException
{
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(passwd.getBytes("UTF-8"));
String hash = new BigInteger(1, crypt.digest()).toString(16);
return hash.substring(0, 6);
}
/**
* Check if USession is of admin
* @param session
* @return
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
public boolean isAdmin(USession session) throws NoSuchAlgorithmException, UnsupportedEncodingException
{
if(session.trip.toLowerCase().equals(calcHash(Config.password).toLowerCase()))
{
return true;
}
return false;
}
/**
* Check if USession is of mod
* @param session
* @return
* @throws NoSuchAlgorithmException
* @throws UnsupportedEncodingException
*/
public boolean isMod(USession session) throws NoSuchAlgorithmException, UnsupportedEncodingException
{
for(String trip: Config.mods)
{
if(session.trip.toLowerCase().equals(trip.toLowerCase()))
{
return true;
}
}
return false;
}
}

2
web/META-INF/context.xml Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/"/>

24
web/base16/android.less Normal file
View File

@ -0,0 +1,24 @@
//
// Base16 Bright
// Created by Fredrik Broman (http://frebro.com)
// Based on the Android visual guidelines
// http://developer.android.com/design/style/color.html
//
@base00: #212121;
@base01: #333333;
@base02: #626261;
@base03: #858585;
@base04: #dddddd;
@base05: #e0e0e0;
@base06: #f2f2f2;
@base07: #ffffff;
@base08: #e94749;
@base09: #f18618;
@base0A: #fbba37;
@base0B: #99C21D;
@base0C: #33B5E5;
@base0D: #0099CC;
@base0E: #9568AA;
@base0F: #754595;

View File

@ -0,0 +1,21 @@
//
// Base16 Atelier Dune
// Created by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/)
//
@base00: #20201d;
@base01: #292824;
@base02: #6e6b5e;
@base03: #7d7a68;
@base04: #999580;
@base05: #a6a28c;
@base06: #e8e4cf;
@base07: #fefbec;
@base08: #d73737;
@base09: #b65611;
@base0A: #cfb017;
@base0B: #60ac39;
@base0C: #1fad83;
@base0D: #6684e1;
@base0E: #b854d4;
@base0F: #d43552;

View File

@ -0,0 +1,21 @@
//
// Base16 Atelier Forest
// Created by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/)
//
@base00: #1b1918;
@base01: #2c2421;
@base02: #68615e;
@base03: #766e6b;
@base04: #9c9491;
@base05: #a8a19f;
@base06: #e6e2e0;
@base07: #f1efee;
@base08: #f22c40;
@base09: #df5320;
@base0A: #d5911a;
@base0B: #5ab738;
@base0C: #00ad9c;
@base0D: #407ee7;
@base0E: #6666ea;
@base0F: #c33ff3;

View File

@ -0,0 +1,21 @@
//
// Base16 Atelier Heath
// Created by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/)
//
@base00: #1b181b;
@base01: #292329;
@base02: #695d69;
@base03: #776977;
@base04: #9e8f9e;
@base05: #ab9bab;
@base06: #d8cad8;
@base07: #f7f3f7;
@base08: #ca402b;
@base09: #a65926;
@base0A: #bb8a35;
@base0B: #379a37;
@base0C: #159393;
@base0D: #516aec;
@base0E: #7b59c0;
@base0F: #cc33cc;

View File

@ -0,0 +1,21 @@
//
// Base16 Atelier Lakeside
// Created by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/)
//
@base00: #161b1d;
@base01: #1f292e;
@base02: #516d7b;
@base03: #5a7b8c;
@base04: #7195a8;
@base05: #7ea2b4;
@base06: #c1e4f6;
@base07: #ebf8ff;
@base08: #d22d72;
@base09: #935c25;
@base0A: #8a8a0f;
@base0B: #568c3b;
@base0C: #2d8f6f;
@base0D: #257fad;
@base0E: #5d5db1;
@base0F: #b72dd2;

View File

@ -0,0 +1,21 @@
//
// Base16 Atelier Seaside
// Created by Bram de Haan (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/)
//
@base00: #131513;
@base01: #242924;
@base02: #5e6e5e;
@base03: #687d68;
@base04: #809980;
@base05: #8ca68c;
@base06: #cfe8cf;
@base07: #f0fff0;
@base08: #e6193c;
@base09: #87711d;
@base0A: #c3c322;
@base0B: #29a329;
@base0C: #1999b3;
@base0D: #3d62f5;
@base0E: #ad2bee;
@base0F: #e619c3;

21
web/base16/bright.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Bright
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #000000;
@base01: #303030;
@base02: #505050;
@base03: #b0b0b0;
@base04: #d0d0d0;
@base05: #e0e0e0;
@base06: #f5f5f5;
@base07: #ffffff;
@base08: #fb0120;
@base09: #fc6d24;
@base0A: #fda331;
@base0B: #a1c659;
@base0C: #76c7b7;
@base0D: #6fb3d2;
@base0E: #d381c3;
@base0F: #be643c;

21
web/base16/chalk.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Chalk
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #151515;
@base01: #202020;
@base02: #303030;
@base03: #505050;
@base04: #b0b0b0;
@base05: #d0d0d0;
@base06: #e0e0e0;
@base07: #f5f5f5;
@base08: #fb9fb1;
@base09: #eda987;
@base0A: #ddb26f;
@base0B: #acc267;
@base0C: #12cfc0;
@base0D: #6fc2ef;
@base0E: #e1a3ee;
@base0F: #deaf8f;

21
web/base16/default.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Default
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #151515;
@base01: #202020;
@base02: #303030;
@base03: #505050;
@base04: #b0b0b0;
@base05: #d0d0d0;
@base06: #e0e0e0;
@base07: #f5f5f5;
@base08: #ac4142;
@base09: #d28445;
@base0A: #f4bf75;
@base0B: #90a959;
@base0C: #75b5aa;
@base0D: #6a9fb5;
@base0E: #aa759f;
@base0F: #8f5536;

21
web/base16/eighties.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Eighties
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #2d2d2d;
@base01: #393939;
@base02: #515151;
@base03: #747369;
@base04: #a09f93;
@base05: #d3d0c8;
@base06: #e8e6df;
@base07: #f2f0ec;
@base08: #f2777a;
@base09: #f99157;
@base0A: #ffcc66;
@base0B: #99cc99;
@base0C: #66cccc;
@base0D: #6699cc;
@base0E: #cc99cc;
@base0F: #d27b53;

View File

@ -0,0 +1,21 @@
//
// Base16 Greenscreen
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #001100;
@base01: #003300;
@base02: #005500;
@base03: #007700;
@base04: #009900;
@base05: #00bb00;
@base06: #00dd00;
@base07: #00ff00;
@base08: #007700;
@base09: #009900;
@base0A: #007700;
@base0B: #00bb00;
@base0C: #005500;
@base0D: #009900;
@base0E: #00bb00;
@base0F: #005500;

21
web/base16/mocha.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Mocha
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #3B3228;
@base01: #534636;
@base02: #645240;
@base03: #7e705a;
@base04: #b8afad;
@base05: #d0c8c6;
@base06: #e9e1dd;
@base07: #f5eeeb;
@base08: #cb6077;
@base09: #d28b71;
@base0A: #f4bc87;
@base0B: #beb55b;
@base0C: #7bbda4;
@base0D: #8ab3b5;
@base0E: #a89bb9;
@base0F: #bb9584;

21
web/base16/monokai.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Monokai
// Created by Wimer Hazenberg (http://www.monokai.nl)
//
@base00: #272822;
@base01: #383830;
@base02: #49483e;
@base03: #75715e;
@base04: #a59f85;
@base05: #f8f8f2;
@base06: #f5f4f1;
@base07: #f9f8f5;
@base08: #f92672;
@base09: #fd971f;
@base0A: #f4bf75;
@base0B: #a6e22e;
@base0C: #a1efe4;
@base0D: #66d9ef;
@base0E: #ae81ff;
@base0F: #cc6633;

26
web/base16/nese.less Normal file
View File

@ -0,0 +1,26 @@
//
// Base16 NE.se color system
// Created by Fredrik Broman (http://frebro.com)
//
// Reference on Adobe Kuler:
// https://kuler.adobe.com/NESE-Warm-color-theme-2900819/
// https://kuler.adobe.com/NESE-Cold-color-theme-2900847/
//
@base00: lighten(black, 1%); // #030303
@base01: lighten(black, 5%); // #0c0c0c
@base02: lighten(black, 20%); // #262626
@base03: lighten(black, 45%); // #5f5f5f
@base04: lighten(black, 65%); // #959595
@base05: lighten(black, 80%); // #c1c1c1
@base06: lighten(black, 95%); // #efefef
@base07: lighten(black, 99%); // #fcfcfc
@base08: #f73e30; // Red
@base09: mix(@base08, @base0A, 50%); // Orange = #f97c37
@base0A: #faba3d; // Yellow
@base0B: #1ab857; // Green
@base0C: mix(@base0B, @base0D, 50%); // Cyan = #29a188
@base0D: #388bb8; // Blue
@base0E: mix(@base0D, @base0F, 50%); // Purple = #9c73a7
@base0F: #ff5a96; // Pink

21
web/base16/ocean.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Ocean
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #2b303b;
@base01: #343d46;
@base02: #4f5b66;
@base03: #65737e;
@base04: #a7adba;
@base05: #c0c5ce;
@base06: #dfe1e8;
@base07: #eff1f5;
@base08: #bf616a;
@base09: #d08770;
@base0A: #ebcb8b;
@base0B: #a3be8c;
@base0C: #96b5b4;
@base0D: #8fa1b3;
@base0E: #b48ead;
@base0F: #ab7967;

21
web/base16/pop.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Pop
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #000000;
@base01: #202020;
@base02: #303030;
@base03: #505050;
@base04: #b0b0b0;
@base05: #d0d0d0;
@base06: #e0e0e0;
@base07: #ffffff;
@base08: #eb008a;
@base09: #f29333;
@base0A: #f8ca12;
@base0B: #37b349;
@base0C: #00aabb;
@base0D: #0e5a94;
@base0E: #b31e8d;
@base0F: #7a2d00;

View File

@ -0,0 +1,21 @@
//
// Base16 Railscasts
// Created by Ryan Bates (http://railscasts.com)
//
@base00: #2b2b2b;
@base01: #272935;
@base02: #3a4055;
@base03: #5a647e;
@base04: #d4cfc9;
@base05: #e6e1dc;
@base06: #f4f1ed;
@base07: #f9f7f3;
@base08: #da4939;
@base09: #cc7833;
@base0A: #ffc66d;
@base0B: #a5c261;
@base0C: #519f50;
@base0D: #6d9cbe;
@base0E: #b6b3eb;
@base0F: #bc9458;

21
web/base16/solarized.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Solarized
// Created by Ethan Schoonover (http://ethanschoonover.com/solarized)
//
@base00: #002b36;
@base01: #073642;
@base02: #586e75;
@base03: #657b83;
@base04: #839496;
@base05: #93a1a1;
@base06: #eee8d5;
@base07: #fdf6e3;
@base08: #dc322f;
@base09: #cb4b16;
@base0A: #b58900;
@base0B: #859900;
@base0C: #2aa198;
@base0D: #268bd2;
@base0E: #6c71c4;
@base0F: #d33682;

21
web/base16/tomorrow.less Normal file
View File

@ -0,0 +1,21 @@
//
// Base16 Tomorrow
// Created by Chris Kempson (http://chriskempson.com)
//
@base00: #1d1f21;
@base01: #282a2e;
@base02: #373b41;
@base03: #969896;
@base04: #b4b7b4;
@base05: #c5c8c6;
@base06: #e0e0e0;
@base07: #ffffff;
@base08: #cc6666;
@base09: #de935f;
@base0A: #f0c674;
@base0B: #b5bd68;
@base0C: #8abeb7;
@base0D: #81a2be;
@base0E: #b294bb;
@base0F: #a3685a;

537
web/client.js Normal file
View File

@ -0,0 +1,537 @@
var frontpage = [
" _ _ ___ _ _ ",
" | || || _|| |_ ___| |_ __",
" | || || |_ | |_ || _||_ |",
" |____||___||_|_|__/|_| |__|",
"",
"",
"Welcome to UChats, a minimal, distraction-free chat application made with java. It is clone of Hack.chat(https://github.com/AndrewBelt/hack.chat)",
"Channels are created and joined by going to https://uchats.herokuapp.com/?your-channel. There are no channel lists, so a secret channel name can be used for private discussions.",
"",
"Here are some pre-made channels you can join:",
"?devs ?meta",
"?math ?physics ?chemistry",
"?technology ?programming",
"?games ?banana",
"And here's a random one generated just for you: ?" + Math.random().toString(36).substr(2, 8),
"",
"",
"Formatting:",
"Whitespace is preserved, so source code can be pasted verbatim.",
"Surround LaTeX with a dollar sign for inline style $\\zeta(2) = \\pi^2/6$, and two dollars for display. $$\\int_0^1 \\int_0^1 \\frac{1}{1-xy} dx dy = \\frac{\\pi^2}{6}$$",
"",
"GitHub: https://github.com/PiyushXCoder/uchat",
"",
"Server and web client released under the MIT license.",
"No message history is retained on the UChats server.",
].join("\n")
function $(query) {return document.querySelector(query)}
function localStorageGet(key) {
try {
return window.localStorage[key]
}
catch(e) {}
}
function localStorageSet(key, val) {
try {
window.localStorage[key] = val
}
catch(e) {}
}
var ws
var myNick = localStorageGet('my-nick')
var myChannel = window.location.search.replace(/^\?/, '')
var lastSent = [""]
var lastSentPos = 0
// Ping server every 50 seconds to retain WebSocket connection
window.setInterval(function() {
send({cmd: 'ping'})
}, 50000)
function join(channel) {
if (document.domain == 'uchats.herokuapp.com') {
ws = new WebSocket('wss://uchats.herokuapp.com/uchatsserver')
}
else {
// for local installs
ws = new WebSocket('ws://' + location.hostname+":"+location.port + '/uchatsserver')
}
var wasConnected = false
ws.onopen = function() {
if (!wasConnected) {
if (location.hash) {
myNick = location.hash.substr(1)
}
else {
myNick = prompt('Nickname:', myNick)
}
}
if (myNick) {
localStorageSet('my-nick', myNick)
send({cmd: 'join', channel: channel, nick: myNick})
}
wasConnected = true
}
ws.onclose = function() {
if (wasConnected) {
pushMessage({nick: '!', text: "Server disconnected. Attempting to reconnect..."})
}
window.setTimeout(function() {
join(channel)
}, 2000)
}
ws.onmessage = function(message) {
var args = JSON.parse(message.data)
var cmd = args.cmd
var command = COMMANDS[cmd]
command.call(null, args)
}
}
var COMMANDS = {
chat: function(args) {
if (ignoredUsers.indexOf(args.nick) >= 0) {
return
}
pushMessage(args)
},
info: function(args) {
args.nick = '*'
pushMessage(args)
},
warn: function(args) {
args.nick = '!'
pushMessage(args)
},
onlineSet: function(args) {
var nicks = args.nicks
usersClear()
nicks.forEach(function(nick) {
userAdd(nick)
})
pushMessage({nick: '*', text: "Users online: " + nicks.join(", ")})
},
onlineAdd: function(args) {
var nick = args.nick
userAdd(nick)
if ($('#joined-left').checked) {
pushMessage({nick: '*', text: nick + " joined"})
}
},
onlineRemove: function(args) {
var nick = args.nick
userRemove(nick)
if ($('#joined-left').checked) {
pushMessage({nick: '*', text: nick + " left"})
}
},
}
function pushMessage(args) {
// Message container
var messageEl = document.createElement('div')
messageEl.classList.add('message')
if (args.nick == myNick) {
messageEl.classList.add('me')
}
else if (args.nick == '!') {
messageEl.classList.add('warn')
}
else if (args.nick == '*') {
messageEl.classList.add('info')
}
else if (args.admin) {
messageEl.classList.add('admin')
}
else if (args.mod) {
messageEl.classList.add('mod')
}
// Nickname
var nickSpanEl = document.createElement('span')
nickSpanEl.classList.add('nick')
messageEl.appendChild(nickSpanEl)
if (args.trip) {
var tripEl = document.createElement('span')
tripEl.textContent = args.trip + " "
tripEl.classList.add('trip')
nickSpanEl.appendChild(tripEl)
}
if (args.nick) {
var nickLinkEl = document.createElement('a')
nickLinkEl.textContent = args.nick
nickLinkEl.onclick = function() {
insertAtCursor("@" + args.nick + " ")
$('#chatinput').focus()
}
var date = new Date(args.time || Date.now())
nickLinkEl.title = date.toLocaleString()
nickSpanEl.appendChild(nickLinkEl)
}
// Text
var textEl = document.createElement('pre')
textEl.classList.add('text')
textEl.textContent = args.text || ''
textEl.innerHTML = textEl.innerHTML.replace(/(\?|https?:\/\/)\S+?(?=[,.!?:)]?\s|$)/g, parseLinks)
if ($('#parse-latex').checked) {
// Temporary hotfix for \rule spamming, see https://github.com/Khan/KaTeX/issues/109
textEl.innerHTML = textEl.innerHTML.replace(/\\rule|\\\\\s*\[.*?\]/g, '')
try {
renderMathInElement(textEl, {delimiters: [
{left: "$$", right: "$$", display: true},
{left: "$", right: "$", display: false},
]})
}
catch (e) {
console.warn(e)
}
}
messageEl.appendChild(textEl)
// Scroll to bottom
var atBottom = isAtBottom()
$('#messages').appendChild(messageEl)
if (atBottom) {
window.scrollTo(0, document.body.scrollHeight)
}
unread += 1
updateTitle()
}
function insertAtCursor(text) {
var input = $('#chatinput')
var start = input.selectionStart || 0
var before = input.value.substr(0, start)
var after = input.value.substr(start)
before += text
input.value = before + after
input.selectionStart = input.selectionEnd = before.length
updateInputSize()
}
function send(data) {
if (ws && ws.readyState == ws.OPEN) {
ws.send(JSON.stringify(data))
}
}
function parseLinks(g0) {
var a = document.createElement('a')
a.innerHTML = g0
var url = a.textContent
a.href = url
a.target = '_blank'
return a.outerHTML
}
var windowActive = true
var unread = 0
window.onfocus = function() {
windowActive = true
updateTitle()
}
window.onblur = function() {
windowActive = false
}
window.onscroll = function() {
if (isAtBottom()) {
updateTitle()
}
}
function isAtBottom() {
return (window.innerHeight + window.scrollY) >= (document.body.scrollHeight - 1)
}
function updateTitle() {
if (windowActive && isAtBottom()) {
unread = 0
}
var title
if (myChannel) {
title = "?" + myChannel
}
else {
title = "UChats"
}
if (unread > 0) {
title = '(' + unread + ') ' + title
}
document.title = title
}
/* footer */
$('#footer').onclick = function() {
$('#chatinput').focus()
}
$('#chatinput').onkeydown = function(e) {
if (e.keyCode == 13 /* ENTER */ && !e.shiftKey) {
e.preventDefault()
// Submit message
if (e.target.value != '') {
var text = e.target.value
e.target.value = ''
send({cmd: 'chat', text: text})
lastSent[0] = text
lastSent.unshift("")
lastSentPos = 0
updateInputSize()
}
}
else if (e.keyCode == 38 /* UP */) {
// Restore previous sent messages
if (e.target.selectionStart === 0 && lastSentPos < lastSent.length - 1) {
e.preventDefault()
if (lastSentPos == 0) {
lastSent[0] = e.target.value
}
lastSentPos += 1
e.target.value = lastSent[lastSentPos]
e.target.selectionStart = e.target.selectionEnd = e.target.value.length
updateInputSize()
}
}
else if (e.keyCode == 40 /* DOWN */) {
if (e.target.selectionStart === e.target.value.length && lastSentPos > 0) {
e.preventDefault()
lastSentPos -= 1
e.target.value = lastSent[lastSentPos]
e.target.selectionStart = e.target.selectionEnd = 0
updateInputSize()
}
}
else if (e.keyCode == 27 /* ESC */) {
e.preventDefault()
// Clear input field
e.target.value = ""
lastSentPos = 0
lastSent[lastSentPos] = ""
updateInputSize()
}
else if (e.keyCode == 9 /* TAB */) {
// Tab complete nicknames starting with @
e.preventDefault()
var pos = e.target.selectionStart || 0
var text = e.target.value
var index = text.lastIndexOf('@', pos)
if (index >= 0) {
var stub = text.substring(index + 1, pos).toLowerCase()
// Search for nick beginning with stub
var nicks = onlineUsers.filter(function(nick) {
return nick.toLowerCase().indexOf(stub) == 0
})
if (nicks.length == 1) {
insertAtCursor(nicks[0].substr(stub.length) + " ")
}
}
}
}
function updateInputSize() {
var atBottom = isAtBottom()
var input = $('#chatinput')
input.style.height = 0
input.style.height = input.scrollHeight + 'px'
document.body.style.marginBottom = $('#footer').offsetHeight + 'px'
if (atBottom) {
window.scrollTo(0, document.body.scrollHeight)
}
}
$('#chatinput').oninput = function() {
updateInputSize()
}
updateInputSize()
/* sidebar */
$('#sidebar').onmouseenter = $('#sidebar').ontouchstart = function(e) {
$('#sidebar-content').classList.remove('hidden')
e.stopPropagation()
}
$('#sidebar').onmouseleave = document.ontouchstart = function() {
if (!$('#pin-sidebar').checked) {
$('#sidebar-content').classList.add('hidden')
}
}
$('#clear-messages').onclick = function() {
// Delete children elements
var messages = $('#messages')
while (messages.firstChild) {
messages.removeChild(messages.firstChild)
}
}
// Restore settings from localStorage
if (localStorageGet('pin-sidebar') == 'true') {
$('#pin-sidebar').checked = true
$('#sidebar-content').classList.remove('hidden')
}
if (localStorageGet('joined-left') == 'false') {
$('#joined-left').checked = false
}
if (localStorageGet('parse-latex') == 'false') {
$('#parse-latex').checked = false
}
$('#pin-sidebar').onchange = function(e) {
localStorageSet('pin-sidebar', !!e.target.checked)
}
$('#joined-left').onchange = function(e) {
localStorageSet('joined-left', !!e.target.checked)
}
$('#parse-latex').onchange = function(e) {
localStorageSet('parse-latex', !!e.target.checked)
}
// User list
var onlineUsers = []
var ignoredUsers = []
function userAdd(nick) {
var user = document.createElement('a')
user.textContent = nick
user.onclick = function(e) {
userInvite(nick)
}
var userLi = document.createElement('li')
userLi.appendChild(user)
$('#users').appendChild(userLi)
onlineUsers.push(nick)
}
function userRemove(nick) {
var users = $('#users')
var children = users.children
for (var i = 0; i < children.length; i++) {
var user = children[i]
if (user.textContent == nick) {
users.removeChild(user)
}
}
var index = onlineUsers.indexOf(nick)
if (index >= 0) {
onlineUsers.splice(index, 1)
}
}
function usersClear() {
var users = $('#users')
while (users.firstChild) {
users.removeChild(users.firstChild)
}
onlineUsers.length = 0
}
function userInvite(nick) {
send({cmd: 'invite', nick: nick})
}
function userIgnore(nick) {
ignoredUsers.push(nick)
}
/* color scheme switcher */
var schemes = [
'android',
'atelier-dune',
'atelier-forest',
'atelier-heath',
'atelier-lakeside',
'atelier-seaside',
'bright',
'chalk',
'default',
'eighties',
'greenscreen',
'mocha',
'monokai',
'nese',
'ocean',
'pop',
'railscasts',
'solarized',
'tomorrow',
]
var currentScheme = 'atelier-dune'
function setScheme(scheme) {
currentScheme = scheme
$('#scheme-link').href = "/schemes/" + scheme + ".css"
localStorageSet('scheme', scheme)
}
// Add scheme options to dropdown selector
schemes.forEach(function(scheme) {
var option = document.createElement('option')
option.textContent = scheme
option.value = scheme
$('#scheme-selector').appendChild(option)
})
$('#scheme-selector').onchange = function(e) {
setScheme(e.target.value)
}
// Load sidebar configaration values from local storage if available
if (localStorageGet('scheme')) {
setScheme(localStorageGet('scheme'))
}
$('#scheme-selector').value = currentScheme
/* main */
if (myChannel == '') {
pushMessage({text: frontpage})
$('#footer').classList.add('hidden')
$('#sidebar').classList.add('hidden')
}
else {
join(myChannel)
}

37
web/index.jsp Normal file
View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<title>hack.chat</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="katex/katex.min.css">
<link id="scheme-link" rel="stylesheet" href="schemes/atelier-dune.css">
<script src="katex/katex.min.js"></script><script src="katex/contrib/auto-render.min.js"></script>
</head>
<body>
<article class="container">
<div id="messages" class="messages"></div>
</article>
<footer id="footer">
<div class="container">
<form id="chatform" class="messages"><textarea id="chatinput" type="text" autocomplete="off" autofocus></textarea></form>
</div>
</footer>
<nav id="sidebar">
<div id="sidebar-content" class="hidden">
<p><input id="pin-sidebar" type="checkbox"><label for="pin-sidebar">Pin sidebar</label></p>
<h4>Settings</h4>
<p><input id="joined-left" type="checkbox" checked><label for="joined-left">Join/left notify</label></p>
<p><input id="parse-latex" type="checkbox" checked><label for="parse-latex">Parse LaTeX</label></p>
<p><button id="clear-messages">Clear messages</button></p>
<h4>Color scheme</h4>
<select id="scheme-selector"></select>
<h4>Users online</h4>
<p>(Click user to invite)</p>
<ul id="users"></ul>
</div>
</nav>
<script src="client.js"></script>
</body>
</html>

4
web/jquery-3.2.1.min.js vendored Executable file

File diff suppressed because one or more lines are too long

0
web/katex/.gitkeep Normal file
View File

219
web/katex/auto-render.js Normal file
View File

@ -0,0 +1,219 @@
(function(e){if("function"==typeof bootstrap)bootstrap("rendermathinelement",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeRenderMathInElement=e}else"undefined"!=typeof window?window.renderMathInElement=e():global.renderMathInElement=e()})(function(){var define,ses,bootstrap,module,exports;
return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global katex */
var splitAtDelimiters = require("./splitAtDelimiters");
var splitWithDelimiters = function(text, delimiters) {
var data = [{type: "text", data: text}];
for (var i = 0; i < delimiters.length; i++) {
var delimiter = delimiters[i];
data = splitAtDelimiters(
data, delimiter.left, delimiter.right,
delimiter.display || false);
}
return data;
};
var renderMathInText = function(text, delimiters) {
var data = splitWithDelimiters(text, delimiters);
var fragment = document.createDocumentFragment();
for (var i = 0; i < data.length; i++) {
if (data[i].type === "text") {
fragment.appendChild(document.createTextNode(data[i].data));
} else {
var span = document.createElement("span");
var math = data[i].data;
try {
katex.render(math, span, {
displayMode: data[i].display
});
} catch (e) {
if (!(e instanceof katex.ParseError)) {
throw e;
}
console.error(
"KaTeX auto-render: Failed to parse `" + data[i].data +
"` with ",
e
);
fragment.appendChild(document.createTextNode(data[i].rawData));
continue;
}
fragment.appendChild(span);
}
}
return fragment;
};
var renderElem = function(elem, delimiters, ignoredTags) {
for (var i = 0; i < elem.childNodes.length; i++) {
var childNode = elem.childNodes[i];
if (childNode.nodeType === 3) {
// Text node
var frag = renderMathInText(childNode.textContent, delimiters);
i += frag.childNodes.length - 1;
elem.replaceChild(frag, childNode);
} else if (childNode.nodeType === 1) {
// Element node
var shouldRender = ignoredTags.indexOf(
childNode.nodeName.toLowerCase()) === -1;
if (shouldRender) {
renderElem(childNode, delimiters, ignoredTags);
}
}
// Otherwise, it's something else, and ignore it.
}
};
var defaultOptions = {
delimiters: [
{left: "$$", right: "$$", display: true},
{left: "\\[", right: "\\]", display: true},
{left: "\\(", right: "\\)", display: false}
// LaTeX uses this, but it ruins the display of normal `$` in text:
// {left: "$", right: "$", display: false}
],
ignoredTags: [
"script", "noscript", "style", "textarea", "pre", "code"
]
};
var extend = function(obj) {
// Adapted from underscore.js' `_.extend`. See LICENSE.txt for license.
var source, prop;
for (var i = 1, length = arguments.length; i < length; i++) {
source = arguments[i];
for (prop in source) {
if (Object.prototype.hasOwnProperty.call(source, prop)) {
obj[prop] = source[prop];
}
}
}
return obj;
};
var renderMathInElement = function(elem, options) {
if (!elem) {
throw new Error("No element provided to render");
}
options = extend({}, defaultOptions, options);
renderElem(elem, options.delimiters, options.ignoredTags);
};
module.exports = renderMathInElement;
},{"./splitAtDelimiters":2}],2:[function(require,module,exports){
var findEndOfMath = function(delimiter, text, startIndex) {
// Adapted from
// https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx
var index = startIndex;
var braceLevel = 0;
var delimLength = delimiter.length;
while (index < text.length) {
var character = text[index];
if (braceLevel <= 0 &&
text.slice(index, index + delimLength) === delimiter) {
return index;
} else if (character === "\\") {
index++;
} else if (character === "{") {
braceLevel++;
} else if (character === "}") {
braceLevel--;
}
index++;
}
return -1;
};
var splitAtDelimiters = function(startData, leftDelim, rightDelim, display) {
var finalData = [];
for (var i = 0; i < startData.length; i++) {
if (startData[i].type === "text") {
var text = startData[i].data;
var lookingForLeft = true;
var currIndex = 0;
var nextIndex;
nextIndex = text.indexOf(leftDelim);
if (nextIndex !== -1) {
currIndex = nextIndex;
finalData.push({
type: "text",
data: text.slice(0, currIndex)
});
lookingForLeft = false;
}
while (true) {
if (lookingForLeft) {
nextIndex = text.indexOf(leftDelim, currIndex);
if (nextIndex === -1) {
break;
}
finalData.push({
type: "text",
data: text.slice(currIndex, nextIndex)
});
currIndex = nextIndex;
} else {
nextIndex = findEndOfMath(
rightDelim,
text,
currIndex + leftDelim.length);
if (nextIndex === -1) {
break;
}
finalData.push({
type: "math",
data: text.slice(
currIndex + leftDelim.length,
nextIndex),
rawData: text.slice(
currIndex,
nextIndex + rightDelim.length),
display: display
});
currIndex = nextIndex + rightDelim.length;
}
lookingForLeft = !lookingForLeft;
}
finalData.push({
type: "text",
data: text.slice(currIndex)
});
} else {
finalData.push(startData[i]);
}
}
return finalData;
};
module.exports = splitAtDelimiters;
},{}]},{},[1])
(1)
});
;

1
web/katex/contrib/auto-render.min.js vendored Normal file
View File

@ -0,0 +1 @@
(function(e){if("function"==typeof bootstrap)bootstrap("rendermathinelement",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeRenderMathInElement=e}else"undefined"!=typeof window?window.renderMathInElement=e():global.renderMathInElement=e()})(function(){var e,t,r,n,a;return function i(e,t,r){function n(o,l){if(!t[o]){if(!e[o]){var f=typeof require=="function"&&require;if(!l&&f)return f(o,!0);if(a)return a(o,!0);throw new Error("Cannot find module '"+o+"'")}var s=t[o]={exports:{}};e[o][0].call(s.exports,function(t){var r=e[o][1][t];return n(r?r:t)},s,s.exports,i,e,t,r)}return t[o].exports}var a=typeof require=="function"&&require;for(var o=0;o<r.length;o++)n(r[o]);return n}({1:[function(e,t,r){var n=e("./splitAtDelimiters");var a=function(e,t){var r=[{type:"text",data:e}];for(var a=0;a<t.length;a++){var i=t[a];r=n(r,i.left,i.right,i.display||false)}return r};var i=function(e,t){var r=a(e,t);var n=document.createDocumentFragment();for(var i=0;i<r.length;i++){if(r[i].type==="text"){n.appendChild(document.createTextNode(r[i].data))}else{var o=document.createElement("span");var l=r[i].data;try{katex.render(l,o,{displayMode:r[i].display})}catch(f){if(!(f instanceof katex.ParseError)){throw f}console.error("KaTeX auto-render: Failed to parse `"+r[i].data+"` with ",f);n.appendChild(document.createTextNode(r[i].rawData));continue}n.appendChild(o)}}return n};var o=function(e,t,r){for(var n=0;n<e.childNodes.length;n++){var a=e.childNodes[n];if(a.nodeType===3){var l=i(a.textContent,t);n+=l.childNodes.length-1;e.replaceChild(l,a)}else if(a.nodeType===1){var f=r.indexOf(a.nodeName.toLowerCase())===-1;if(f){o(a,t,r)}}}};var l={delimiters:[{left:"$$",right:"$$",display:true},{left:"\\[",right:"\\]",display:true},{left:"\\(",right:"\\)",display:false}],ignoredTags:["script","noscript","style","textarea","pre","code"]};var f=function(e){var t,r;for(var n=1,a=arguments.length;n<a;n++){t=arguments[n];for(r in t){if(Object.prototype.hasOwnProperty.call(t,r)){e[r]=t[r]}}}return e};var s=function(e,t){if(!e){throw new Error("No element provided to render")}t=f({},l,t);o(e,t.delimiters,t.ignoredTags)};t.exports=s},{"./splitAtDelimiters":2}],2:[function(e,t,r){var n=function(e,t,r){var n=r;var a=0;var i=e.length;while(n<t.length){var o=t[n];if(a<=0&&t.slice(n,n+i)===e){return n}else if(o==="\\"){n++}else if(o==="{"){a++}else if(o==="}"){a--}n++}return-1};var a=function(e,t,r,a){var i=[];for(var o=0;o<e.length;o++){if(e[o].type==="text"){var l=e[o].data;var f=true;var s=0;var d;d=l.indexOf(t);if(d!==-1){s=d;i.push({type:"text",data:l.slice(0,s)});f=false}while(true){if(f){d=l.indexOf(t,s);if(d===-1){break}i.push({type:"text",data:l.slice(s,d)});s=d}else{d=n(r,l,s+t.length);if(d===-1){break}i.push({type:"math",data:l.slice(s+t.length,d),rawData:l.slice(s,d+r.length),display:a});s=d+r.length}f=!f}i.push({type:"text",data:l.slice(s)})}else{i.push(e[o])}}return i};t.exports=a},{}]},{},[1])(1)});

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More