Browse Source

Initial commit

master
czar 5 months ago
commit
1ad4b98310
Signed by: czar GPG Key ID: 2DBA9558E9277C37
  1. 25
      .gitignore
  2. 32
      build.gradle
  3. BIN
      gradle/wrapper/gradle-wrapper.jar
  4. 5
      gradle/wrapper/gradle-wrapper.properties
  5. 185
      gradlew
  6. 89
      gradlew.bat
  7. 5
      settings.gradle
  8. 3
      src/main/java/META-INF/MANIFEST.MF
  9. 12
      src/main/java/st/kalli/annotation/Registered.java
  10. 132
      src/main/java/st/kalli/ircd/Channel.java
  11. 167
      src/main/java/st/kalli/ircd/Client.java
  12. 48
      src/main/java/st/kalli/ircd/Err.java
  13. 134
      src/main/java/st/kalli/ircd/Handles.java
  14. 193
      src/main/java/st/kalli/ircd/Rpl.java
  15. 203
      src/main/java/st/kalli/ircd/Server.java
  16. 19
      src/main/java/st/kalli/util/Util.java
  17. 8
      src/main/resources/motd.txt

25
.gitignore

@ -0,0 +1,25 @@
# Created by https://www.toptal.com/developers/gitignore/api/gradle
# Edit at https://www.toptal.com/developers/gitignore?templates=gradle
.idea
### Gradle ###
.gradle
build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
# gradle/wrapper/gradle-wrapper.properties
### Gradle Patch ###
**/build/
# End of https://www.toptal.com/developers/gitignore/api/gradle

32
build.gradle

@ -0,0 +1,32 @@
/*
* This file was generated by the Gradle 'init' task.
*/
plugins {
id 'java'
id 'maven-publish'
}
repositories {
mavenLocal()
maven {
url = uri('https://repo.maven.apache.org/maven2/')
}
}
dependencies {
implementation 'org.jetbrains:annotations:16.0.1'
}
group = 'st.kalli.ircd'
version = '1.0-SNAPSHOT'
description = 'ircd'
java.sourceCompatibility = JavaVersion.VERSION_1_8
publishing {
publications {
maven(MavenPublication) {
from(components.java)
}
}
}

BIN
gradle/wrapper/gradle-wrapper.jar

5
gradle/wrapper/gradle-wrapper.properties

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

5
settings.gradle

@ -0,0 +1,5 @@
/*
* This file was generated by the Gradle 'init' task.
*/
rootProject.name = 'ircd'

3
src/main/java/META-INF/MANIFEST.MF

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: st.kalli.ircd.Server

12
src/main/java/st/kalli/annotation/Registered.java

@ -0,0 +1,12 @@
package st.kalli.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Registered {
}

132
src/main/java/st/kalli/ircd/Channel.java

@ -0,0 +1,132 @@
package st.kalli.ircd;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class Channel {
private final Set<Client> members = new HashSet<>();
private final String name;
private final Timestamp created;
private String topic = "";
private String topicNickname = "";
private Timestamp topicTime = null;
private final String mode = "nt";
private static final Server server = Server.get();
public Channel(String name) {
this.name = name;
created = new Timestamp(System.currentTimeMillis());
}
public Set<Client> getMembers() {
return members;
}
public void addMember(Client client) {
members.add(client);
String nickname = client.getNickname();
String username = client.getUsername();
String hostname = server.getHostname();
for(Client member : members){
member.send(String.format(
":%s!%s@%s JOIN %s",
nickname, username, hostname, name
));
}
sendTopic(client);
Rpl.namReply(client, this);
Rpl.endOfNames(client, this);
}
private boolean hasTopic() {
return !topic.isEmpty();
}
public String getName() {
return name;
}
public String getTopic() {
return topic;
}
public String getTopicNickname() {
return topicNickname;
}
public long getTopicTime() {
return topicTime.getTime();
}
public String getNames() {
List<String> names = new ArrayList<>();
for(Client member : members){
names.add(member.getNickname());
}
return String.join(" ",names);
}
public long getCreationTime(){
return created.getTime();
}
public void setTopic(Client client, String topic) {
this.topic = topic;
topicTime = new Timestamp(System.currentTimeMillis());
topicNickname = client.getNickname();
for(Client member: members){
Rpl.topic(member, this);
Rpl.topicWhoTime(member,this);
}
}
public void sendPrivmsg(Client client, String clientMessage) {
String nickname = client.getNickname();
String username = client.getUsername();
String hostname = server.getHostname();
for(Client member : members){
if(member != client){
member.send(String.format(
":%s!%s@%s PRIVMSG %s :%s",
nickname, username, hostname, name, clientMessage
));
}
}
}
public void quit(Client client) {
members.remove(client);
for (Client member : members){
client.sendQuit(member);
}
}
public void sendPart(Client client, String message) {
String nickname = client.getNickname();
String username = client.getUsername();
String hostname = server.getHostname();
for (Client member : members){
member.send(String.format(
":%s!%s@%s PART %s :%s",
nickname, username, hostname, name, message
));
}
}
public String getMode() {
return mode;
}
public void sendTopic(Client client) {
if(hasTopic()) {
Rpl.topic(client, this);
Rpl.topicWhoTime(client, this);
} else {
Rpl.noTopic(client, this);
}
}
}

167
src/main/java/st/kalli/ircd/Client.java

@ -0,0 +1,167 @@
package st.kalli.ircd;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.net.Socket;
import java.util.*;
public class Client {
private final Socket socket;
private final BufferedReader input;
private final PrintWriter output;
private final Server server;
private final Set<Channel> channels = new HashSet<>();
private boolean registered = false;
private String nickname = "*";
private String username = "";
private String realname = "";
private String away = "";
private String quit = "";
public Client(Server server, Socket socket) throws IOException {
this.server = server;
this.socket = socket;
this.output = new PrintWriter(socket.getOutputStream(),false);
this.input = new BufferedReader( new InputStreamReader(socket.getInputStream()));
this.socket.setSoTimeout(1);
}
public void setUsername(String username){
this.username = username;
}
public void setNickname(String nickname){
this.nickname = nickname;
}
public String receiveData() throws IOException {
return input.readLine();
}
public void setRealname(String realname) {
this.realname = realname;
}
public void send(String message){
output.println(message + "\r\n");
if(output.checkError()){
server.disconnect(this);
}
}
public String getNickname() {
return nickname;
}
public boolean isRegistered() {
return registered;
}
public boolean hasDefaultNickname() {
return Objects.equals(nickname, "*");
}
public void register() {
registered = true;
Rpl.welcome(this);
Rpl.yourHost(this);
Rpl.created(this);
Rpl.myInfo(this);
serverMotd();
}
public String getUsername() {
return username;
}
public Set<Channel> getChannels() {
return channels;
}
public boolean hasUsername() {
return !username.isEmpty();
}
public void join(String name) {
Channel channel = server.getChannel(name);
channels.add(channel);
channel.addMember(this);
}
public void serverMotd() {
server.motd(this);
}
public void sendPrivmsg(Client target, String message) {
String targetNickname = target.getNickname();
String hostname = server.getHostname();
target.send(String.format(
":%s!%s@%s PRIVMSG %s :%s",
nickname, username, hostname, targetNickname, message
));
if(target.isAway()){
target.sendAway(this);
}
}
private void sendAway(Client target) {
Rpl.away(target,this, away);
}
private boolean isAway(){
System.out.println(away);
return !away.isEmpty();
}
public void setAway(@Nullable String away) {
this.away = away;
}
public void setQuit(String message) {
quit = message;
}
public void sendQuit(Client target) {
String hostname = server.getHostname();
target.send(String.format(
":%s!%s@%s QUIT :Quit:%s",
nickname, username, hostname, quit
));
}
public void part(Channel channel, String message) {
channels.remove(channel);
if(!message.isEmpty()) {
channel.sendPart(this, message);
} else{
channel.sendPart(this, "");
}
}
public void sendPart(Client target, String message) {
String hostname = server.getHostname();
target.send(String.format(
":%s!%s@%s PART :Quit:%s",
nickname, username, hostname, message
));
}
public String getRealname() {
return realname;
}
public String getNameChannels() {
List<String> names = new ArrayList<>();
for(Channel channel : channels){
names.add(channel.getName());
}
return String.join(" ",names);
}
}

48
src/main/java/st/kalli/ircd/Err.java

@ -0,0 +1,48 @@
package st.kalli.ircd;
import org.jetbrains.annotations.NotNull;
public class Err {
private static final Server server = Server.get();
public static void erroneusNickname(@NotNull Client client, String errNickname) {
String nickname = client.getNickname();
client.send(String.format(
"432 %s %s :Invalid nickname",
nickname,errNickname
));
}
public static void nickNameInUse(@NotNull Client client, String errNickname) {
String nickname = client.getNickname();
client.send(String.format(
"433 %s %s :Nickname is already in use",
nickname, errNickname
));
}
public static void notRegistered(@NotNull Client client) {
String nickname = client.getNickname();
client.send(String.format(
"451 %s :User not registered",
nickname
));
}
public static void noSuchChannel(Client client, String errChannel) {
String nickname = client.getNickname();
client.send(String.format(
"403 %s %s :Invalid channel name",
nickname, errChannel
));
}
public static void needMoreParams(Client client, String command, String reason) {
}
public static void noSuchNick(Client client, String nickname) {
}
}

134
src/main/java/st/kalli/ircd/Handles.java

@ -0,0 +1,134 @@
package st.kalli.ircd;
import org.jetbrains.annotations.NotNull;
import st.kalli.annotation.Registered;
import st.kalli.util.Util;
import java.util.Vector;
public class Handles {
private static final Server server = Server.get();
public static void user(@NotNull Client client, String message, Vector<String> ctx) {
if (!client.isRegistered()) {
String username = ctx.get(0);
String realname = message.split(":",2)[1];
client.setUsername(username);
client.setRealname(realname);
if (!client.hasDefaultNickname()) {
client.register();
}
}
}
public static void nick(Client client, String message, @NotNull Vector<String> ctx) {
String nickname = ctx.get(0);
System.out.println(nickname);
if (Util.nicknameMatch(nickname)) {
if (server.hasNickname(nickname)) {
Err.nickNameInUse(client, nickname);
} else {
server.setNickname(client,nickname);
if (!client.isRegistered() && client.hasUsername()) {
client.register();
}
}
} else {
Err.erroneusNickname(client, nickname);
}
}
@Registered
public static void join(Client client, String message, @NotNull Vector<String> ctx) {
String channel = ctx.get(0);
if (Util.channelMatch(channel)) client.join(channel);
else Err.noSuchChannel(client, channel);
}
@Registered
public static void motd(Client client, String message, @NotNull Vector<String> ctx) {
client.serverMotd();
}
public static void ping(Client client, String message, @NotNull Vector<String> ctx) {
server.pong(client);
}
public static void pong(Client client, String message, @NotNull Vector<String> ctx) {
}
@Registered
public static void mode(Client client, String message, @NotNull Vector<String> ctx) {
Channel channel = server.getChannel(ctx.get(0));
Rpl.channelModeIs(client, channel);
Rpl.creationTime(client, channel);
}
@Registered
public static void topic(Client client, String message, @NotNull Vector<String> ctx) {
Channel channel = server.getChannel(ctx.get(0));
if (ctx.size() > 1) {
String channelTopic = message.split(":", 2)[1];
channel.setTopic(client, channelTopic);
} else {
channel.sendTopic(client);
}
}
@Registered
public static void privmsg(Client client, String message, @NotNull Vector<String> ctx) {
String target = ctx.get(0).toLowerCase();
String clientMessage = message.split(":", 2)[1];
if (server.hasChannel(target)) {
Channel channel = server.getChannel(target);
channel.sendPrivmsg(client, clientMessage);
} else if (server.hasNickname(target)) {
Client member = server.getMember(target);
if(member != client){
client.sendPrivmsg(member, clientMessage);
}
}
}
@Registered
public static void away(Client client, String message, @NotNull Vector<String> ctx) {
if(ctx.size() > 0){
String away = message.split(":",2)[1];
client.setAway(away);
} else{
client.setAway("");
}
}
public static void quit(Client client, String message, @NotNull Vector<String> ctx) {
if(ctx.size() > 1 ){
String msg = message.split(":", 2)[1];
server.disconnect(client,msg);
} else{
server.disconnect(client);
}
}
public static void part(Client client, String message, @NotNull Vector<String> ctx) {
String channelName = ctx.get(0);
Channel channel = server.getChannel(channelName);
String msg = message.split(":", 2)[1];
client.part(channel, msg);
}
public static void whois(Client client, String message, @NotNull Vector<String> ctx) {
String nickname = ctx.get(0);
if(server.hasNickname(nickname)){
Client user = server.getMember(nickname);
Rpl.whoIsUser(client, user);
Rpl.whoIsChannels(client, user);
Rpl.endOfWhois(client, nickname);
} else{
Err.noSuchNick(client,nickname);
}
}
}

193
src/main/java/st/kalli/ircd/Rpl.java

@ -0,0 +1,193 @@
package st.kalli.ircd;
public class Rpl {
private static final Server server = Server.get();
public static void welcome(Client client){
String nickname = client.getNickname();
String username = client.getUsername();
String serverName = server.getName();
String hostname = server.getHostname();
client.send(String.format(
"001 * %s :Welcome to %s %s!%s@%s",
nickname, serverName, nickname, username, hostname
));
}
public static void yourHost(Client client) {
String nickname = client.getNickname();
String serverName = server.getName();
client.send(String.format(
"002 %s :Your host is %s",
nickname, serverName
));
}
public static void created(Client client) {
String nickname = client.getNickname();
client.send(String.format(
"003 %s :This server was created sometime",
nickname
));
}
public static void myInfo(Client client) {
String nickname = client.getNickname();
String serverName = server.getName();
String version = server.getVersion();
client.send(String.format(
"004 %s %s %s",
nickname, serverName, version
));
}
public static void creationTime(Client client, Channel channel) {
String nickname = client.getNickname();
String channelName = channel.getName();
long time = channel.getCreationTime();
client.send(String.format(
"329 * %s %s %s",
nickname, channelName, time
));
}
public static void topic(Client client, Channel channel) {
String nickname = client.getNickname();
String channelName = channel.getName();
String topic = channel.getTopic();
client.send(String.format(
"332 * %s %s :%s",
nickname, channelName, topic
));
}
public static void topicWhoTime(Client client, Channel channel) {
String nickname = client.getNickname();
String channelName = channel.getName();
String topicNickname = channel.getTopicNickname();
long topicTime = channel.getTopicTime();
client.send(String.format(
"333 * %s %s %s %s",
nickname, channelName, topicNickname, topicTime
));
}
public static void noTopic(Client client, Channel channel) {
String nickname = client.getNickname();
String channelName = channel.getName();
client.send(String.format(
"331 * %s %s :%s have no topic",
nickname, channelName, channelName
));
}
public static void namReply(Client client, Channel channel) {
String nickname = client.getNickname();
String channelName = channel.getName();
String names = channel.getNames();
client.send(String.format(
"353 %s = %s :%s",
nickname, channelName, names
));
}
public static void endOfNames(Client client, Channel channel) {
String nickname = client.getNickname();
String channelName = channel.getName();
client.send(String.format(
"353 %s = %s :%s",
nickname, channelName, channelName
));
}
public static void motdStart(Client client) {
String nickname = client.getNickname();
String serverName = server.getName();
client.send(String.format(
"375 %s :- %s Message of the day -",
nickname, serverName
));
}
public static void motd(Client client, String line) {
String nickname = client.getNickname();
client.send(String.format(
"372 %s :- %s",
nickname, line
));
}
public static void endOfMotd(Client client) {
String nickname = client.getNickname();
client.send(String.format(
"376 %s :End of /MOTD command",
nickname
));
}
public static void channelModeIs(Client client, Channel channel) {
String nickname = client.getNickname();
String channelName = channel.getName();
String mode = channel.getMode();
client.send(String.format(
"324 * %s %s +%s",
nickname, channelName, mode
));
}
public static void whoIsUser(Client client, Client user) {
String nickname = client.getNickname();
String userNickname = user.getNickname();
String hostname = server.getHostname();
String userRealname = user.getRealname();
client.send(String.format(
"311 * * %s ~%s %s * :%s",
nickname, userNickname, hostname, userRealname
));
}
public static void whoIsChannels(Client client, Client user) {
String nickname = client.getNickname();
String userNickname = user.getNickname();
String userChannels = user.getNameChannels();
client.send(String.format(
"319 * %s %s :%s",
nickname, userNickname, userChannels
));
}
public static void endOfWhois(Client client, String userNickname) {
String nickname = client.getNickname();
client.send(String.format(
"318 * %s %s :End of /WHOIS list.",
nickname, userNickname
));
}
public static void away(Client client, Client user , String message) {
String nickname = user.getNickname();
client.send(String.format(
"301 * %s :%s",
nickname, message
));
}
}

203
src/main/java/st/kalli/ircd/Server.java

@ -0,0 +1,203 @@
package st.kalli.ircd;
import st.kalli.annotation.Registered;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.*;
import java.util.stream.Stream;
public class Server implements Runnable{
private final String hostname;
private final String name;
private final int port;
private final ServerSocket socket;
private final Vector<Client> clients = new Vector<>();
private final HashMap<String,Channel> channels = new HashMap<>();
private final HashMap<String,Client> nicknames = new HashMap<>();
private final Vector<Client> disconnections = new Vector<>();
private static Server server;
static {
try {
server = new Server();
} catch (IOException e) {
e.printStackTrace();
}
}
private final String version = "0.4";
private Server() throws IOException {
hostname = "kalli.st";
name = "KALLI.ST IRC";
port = 6667;
socket = new ServerSocket(port);
socket.setSoTimeout(1);
}
public static Server get(){
return server;
}
private void process(Client client){
try {
String data = client.receiveData();
if (data == null || data.isEmpty()) {
return;
}
for(String message : data.split("\n")){
System.out.println(client.getNickname()+" | "+data);
handle(client, message);
}
} catch( SocketTimeoutException ignored){
} catch (IOException e) {
e.printStackTrace();
}
}
private void handle(Client client, String message) {
Vector<String> ctx = new Vector<>(Arrays.asList(message.split(" ")));
String command = ctx.get(0).toLowerCase();
ctx.remove(0);
try {
Method method = Handles.class.getMethod(command,Client.class,String.class, Vector.class);
if (method.isAnnotationPresent(Registered.class)){
if(client.isRegistered()) {
method.invoke(null, client, message, ctx);
}
else {
Err.notRegistered(client);
}
} else{
method.invoke(null, client, message, ctx);
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (true) {
for (Iterator<Client> iterator = disconnections.iterator(); iterator.hasNext(); ) {
Client client = iterator.next();
iterator.remove();
String nickname = client.getNickname().toLowerCase();
nicknames.remove(nickname);
for (Channel channel : client.getChannels()) {
channel.quit(client);
}
}
try {
Socket cs = socket.accept();
Client client = new Client(this, cs);
clients.add(client);
} catch (SocketTimeoutException ignored) {
} catch (IOException e) {
e.printStackTrace();
}
for (Client client : clients) {
process(client);
}
}
}
public String getName() {
return name;
}
public String getHostname() {
return hostname;
}
public String getVersion() {
return version;
}
public void setNickname(Client client, String nickname) {
if(client.hasDefaultNickname()) {
nicknames.put(nickname.toLowerCase(),client);
} else{
nicknames.put(nickname.toLowerCase(), nicknames.remove(client.getNickname().toLowerCase()));
}
client.setNickname(nickname);
List<Client> notified = new ArrayList<>();
for(Channel channel : client.getChannels()){
for(Client member : channel.getMembers()){
if(notified.contains(member)) continue;
notified.add(member);
String username = client.getNickname();
String serverName = server.getName();
member.send(String.format(
":@%s!%s@%s NICK %s",
nickname, username, serverName, nickname
));
}
}
}
public Channel getChannel(String name) {
String channelName = name.toLowerCase();
return channels.computeIfAbsent(channelName, Channel -> new Channel(channelName));
}
public void motd(Client client){
Stream<String> stream;
stream = new BufferedReader(new InputStreamReader(Objects.requireNonNull(this.getClass().getResourceAsStream("/motd.txt")))).lines();
Rpl.motdStart(client);
stream.forEach(line -> {
Rpl.motd(client, line);
});
Rpl.endOfMotd(client);
}
public void pong(Client client) {
client.send(String.format(
"PONG %s",
hostname
));
}
public boolean hasChannel(String name) {
return channels.containsKey(name);
}
public boolean hasNickname(String nickname) {
return nicknames.containsKey(nickname.toLowerCase());
}
public Client getMember(String name) {
return nicknames.get(name);
}
public void disconnect(Client client, String message) {
disconnections.add(client);
client.setQuit(message);
}
public void disconnect(Client client) {
disconnections.add(client);
}
public static void main(String[] args) {
Server server = Server.get();
server.run();
}
}

19
src/main/java/st/kalli/util/Util.java

@ -0,0 +1,19 @@
package st.kalli.util;
import java.util.regex.Pattern;
public class Util {
private static final Pattern nicknamePattern = Pattern.compile("^[]\\[`_^{|}A-Za-z][]\\[`_^{|}A-Za-z0-9-]{1,50}$");
private static final Pattern channelPattern = Pattern.compile("^[&#+!][^\\x00\\x07\\x0a\\x0d ,:]{2,50}$");
public static boolean nicknameMatch(String nickname){
return nicknamePattern.matcher(nickname).matches();
}
public static boolean channelMatch(String channel){
return channelPattern.matcher(channel).matches();
}
}

8
src/main/resources/motd.txt

@ -0,0 +1,8 @@
,'.
,' `.
,' ,'`. `. KALLI.ST IRC
,' ,' `. `.
\ ,' ,`. /
\ `. ` `. / ->k-
\ `.,' /
\_______/
Loading…
Cancel
Save