From 313bee505ee71e90a4f6b8d3a4a64c182b9f9637 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Thu, 2 Oct 2014 14:53:22 -0700 Subject: [PATCH 01/22] sbt build --- build.sbt | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 build.sbt diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..d9549db --- /dev/null +++ b/build.sbt @@ -0,0 +1,37 @@ +name := "sirc" + +crossPaths := false + +organization := "com.hanhuy" + +version := "1.1.6-SNAPSHOT" + +// sonatype publishing options follow +publishMavenStyle := true + +publishTo := { + val nexus = "https://oss.sonatype.org/" + if (isSnapshot.value) + Some("snapshots" at nexus + "content/repositories/snapshots") + else + Some("releases" at nexus + "service/local/staging/deploy/maven2") +} + +pomIncludeRepository := { _ => false } + +pomExtra := + + git@github.com:pfn/sIRC.git + scm:git:git@github.com:pfn/sIRC.git + + + + pfnguyen + Perry Nguyen + https://github.com/pfn + + + +licenses := Seq("BSD-style" -> url("http://www.opensource.org/licenses/bsd-license.php")) + +homepage := Some(url("https://github.com/pfn/sIRC")) From f6cc5a9380d271c25278acdce8da2dacaa8173d8 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Wed, 5 Nov 2014 14:11:07 -0800 Subject: [PATCH 02/22] sbt build update --- build.sbt | 5 +++++ project/build.properties | 1 + 2 files changed, 6 insertions(+) create mode 100644 project/build.properties diff --git a/build.sbt b/build.sbt index d9549db..323a9ce 100644 --- a/build.sbt +++ b/build.sbt @@ -35,3 +35,8 @@ pomExtra := licenses := Seq("BSD-style" -> url("http://www.opensource.org/licenses/bsd-license.php")) homepage := Some(url("https://github.com/pfn/sIRC")) + +libraryDependencies ++= Seq( + "com.novocode" % "junit-interface" % "0.11" % "test", + "org.mockito" % "mockito-core" % "1.8.5" % "test" +) diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..64abd37 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.6 From d8214ac9aa215d17680fdda7bc74aabe29994d00 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Tue, 7 Oct 2014 08:11:53 -0700 Subject: [PATCH 03/22] Add more durability from server lines --- src/main/java/com/sorcix/sirc/IrcInput.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sorcix/sirc/IrcInput.java b/src/main/java/com/sorcix/sirc/IrcInput.java index 6f5228d..5a5ca8c 100644 --- a/src/main/java/com/sorcix/sirc/IrcInput.java +++ b/src/main/java/com/sorcix/sirc/IrcInput.java @@ -110,9 +110,11 @@ public void run() { // always respond to PING if (line.startsWith("PING ")) { this.irc.out.pong(line.substring(5)); - } else { + } else if (!line.trim().isEmpty()){ this.handleLine(line); - } + } else { + IrcDebug.log("*** Invalid line from server, ignoring"); + } } } catch (final SocketException ex) { this.irc.setConnected(false); From bb379f8084130cdf47e656da96b62475b4f7f3f1 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Wed, 5 Nov 2014 14:11:36 -0800 Subject: [PATCH 04/22] capability negotiation and server-time implementation --- src/main/java/com/sorcix/sirc/Channel.java | 10 +- .../java/com/sorcix/sirc/ClientState.java | 6 +- .../java/com/sorcix/sirc/IrcConnection.java | 29 ++- src/main/java/com/sorcix/sirc/IrcOutput.java | 6 +- src/main/java/com/sorcix/sirc/IrcPacket.java | 39 ++- .../com/sorcix/sirc/IrcPacketFactory.java | 11 + .../com/sorcix/sirc/cap/CapNegotiator.java | 114 ++++++++ .../sorcix/sirc/cap/CompoundNegotiator.java | 73 ++++++ .../sorcix/sirc/cap/ServerTimeNegotiator.java | 37 +++ .../java/com/sorcix/sirc/event/BaseEvent.java | 19 ++ .../sirc/event/MessageEventListener.java | 137 ++++++++++ .../sirc/event/ServerEventListener.java | 245 ++++++++++++++++++ .../java/com/sorcix/sirc/IrcPacketTest.java | 67 +++++ 13 files changed, 776 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/sorcix/sirc/cap/CapNegotiator.java create mode 100644 src/main/java/com/sorcix/sirc/cap/CompoundNegotiator.java create mode 100644 src/main/java/com/sorcix/sirc/cap/ServerTimeNegotiator.java create mode 100644 src/main/java/com/sorcix/sirc/event/BaseEvent.java create mode 100644 src/main/java/com/sorcix/sirc/event/MessageEventListener.java create mode 100644 src/main/java/com/sorcix/sirc/event/ServerEventListener.java create mode 100644 src/test/java/com/sorcix/sirc/IrcPacketTest.java diff --git a/src/main/java/com/sorcix/sirc/Channel.java b/src/main/java/com/sorcix/sirc/Channel.java index 87ebf96..3297d4f 100644 --- a/src/main/java/com/sorcix/sirc/Channel.java +++ b/src/main/java/com/sorcix/sirc/Channel.java @@ -47,7 +47,7 @@ public final class Channel { /** The user list. */ private Map users; /** Possible channel prefixes. */ - protected static final String CHANNEL_PREFIX = "#&+!"; + public static final String CHANNEL_PREFIX = "#&+!"; /** * Creates a new {@code Channel} object with given name. @@ -57,7 +57,7 @@ public final class Channel { * channel. * @param global Whether this object is going to be shared. */ - protected Channel(final String name, final IrcConnection irc, final boolean global) { + public Channel(final String name, final IrcConnection irc, final boolean global) { this.name = name; this.irc = irc; if (global) { @@ -72,7 +72,7 @@ protected Channel(final String name, final IrcConnection irc, final boolean glob * * @param user The user to add. */ - protected void addUser(final User user) { + public void addUser(final User user) { if ((this.users != null) && !this.users.containsKey(user.getNickLower())) { this.users.put(user.getNickLower(), user); } @@ -358,7 +358,7 @@ public void removeOperator(final User user) { * * @param user The user to remove. */ - protected void removeUser(final User user) { + public void removeUser(final User user) { if ((this.users != null) && this.users.containsKey(user.getNickLower())) { this.users.remove(user.getNickLower()); } @@ -501,7 +501,7 @@ public String toString() { * didn't exist. * @return The updated shared User object. */ - protected User updateUser(final User user, final boolean createNew) { + public User updateUser(final User user, final boolean createNew) { if (this.hasUser(user.getNickLower())) { // update user if it exists final User shared = this.getUser(user.getNickLower()); diff --git a/src/main/java/com/sorcix/sirc/ClientState.java b/src/main/java/com/sorcix/sirc/ClientState.java index f41bd74..74b9cd7 100644 --- a/src/main/java/com/sorcix/sirc/ClientState.java +++ b/src/main/java/com/sorcix/sirc/ClientState.java @@ -61,7 +61,7 @@ protected ClientState() { * @param channel * The channel to add. */ - protected void addChannel(final Channel channel) { + public void addChannel(final Channel channel) { if (!this.channels.containsKey(channel.getName().toLowerCase())) { this.channels.put(channel.getName().toLowerCase(), channel); } @@ -100,7 +100,7 @@ protected Channel getChannel(final Channel channel) { * @return The channel, or null if this channel doesn't exist. (The local * user is not in that channel) */ - protected Channel getChannel(final String channel) { + public Channel getChannel(final String channel) { if (channel != null && this.channels.containsKey(channel.toLowerCase())) { return this.channels.get(channel.toLowerCase()); } @@ -165,7 +165,7 @@ protected void removeAll() { * @param channel * The channel name. */ - protected void removeChannel(final String channel) { + public void removeChannel(final String channel) { if (channel != null && this.channels.containsKey(channel.toLowerCase())) { this.channels.remove(channel.toLowerCase()); } diff --git a/src/main/java/com/sorcix/sirc/IrcConnection.java b/src/main/java/com/sorcix/sirc/IrcConnection.java index 98fad80..cf90f4d 100644 --- a/src/main/java/com/sorcix/sirc/IrcConnection.java +++ b/src/main/java/com/sorcix/sirc/IrcConnection.java @@ -27,6 +27,8 @@ */ package com.sorcix.sirc; +import com.sorcix.sirc.cap.CapNegotiator; + import javax.net.SocketFactory; import javax.net.ssl.SSLContext; import java.io.IOException; @@ -60,6 +62,8 @@ public class IrcConnection { public static final String VERSION = "1.1.6-SNAPSHOT"; /** Advanced listener. */ private AdvancedListener advancedListener = null; + + private CapNegotiator.Listener capNegotiatorListener = null; /** Connection InputStream thread. */ private IrcInput in = null; /** Outgoing message delay. (Flood control) */ @@ -234,7 +238,7 @@ public void sendRaw(final String line) { * @param channel * The channel to request the userlist for. */ - protected void askNames(final Channel channel) { + public void askNames(final Channel channel) { this.out.send(IrcPacketFactory.createNAMES(channel.getName())); } @@ -369,10 +373,17 @@ public void connect(Socket sock) throws UnknownHostException, IOException, NickN this.socket = sock; reconnecting = false; } - // open streams + // open streams this.out = new IrcOutput(this, new OutputStreamWriter(this.socket.getOutputStream(), this.charset)); this.in = new IrcInput(this, new InputStreamReader(this.socket.getInputStream(), this.charset)); + CapNegotiator capNegotiator = new CapNegotiator(out); + if (capNegotiatorListener != null) + capNegotiator.addListener(capNegotiatorListener); if (!reconnecting) { + // start capability negotiation + if (capNegotiatorListener != null) { + this.out.sendNowEx(IrcPacketFactory.createCAPLS()); + } // send password if given if (this.server.getPassword() != null) { this.out.sendNowEx(IrcPacketFactory.createPASS(this.server @@ -388,10 +399,14 @@ public void connect(Socket sock) throws UnknownHostException, IOException, NickN loop: while ((line = this.in.getReader().readLine()) != null) { IrcDebug.log(line); final IrcPacket decoder = new IrcPacket(line, this); - if (decoder.isNumeric()) { + if (capNegotiator.isNegotiating()) { + capNegotiator.process(decoder); + } + if (decoder.isNumeric()) { final int command = decoder.getNumericCommand(); switch (command) { case 1: + capNegotiator.cancel(); case 2: case 3: { final String nick = decoder.getArgumentsArray()[0]; @@ -411,7 +426,9 @@ public void connect(Socket sock) throws UnknownHostException, IOException, NickN throw new PasswordException("Invalid password"); } // break; unnecessary due to throw } - } + } else if ("CAP".equals(decoder.getCommand())) { + capNegotiator.process(decoder); + } if (line.startsWith("PING ")) { this.out.pong(line.substring(5)); } @@ -780,6 +797,10 @@ public void setAdvancedListener(final AdvancedListener listener) { this.advancedListener = listener; } + public void setCapNegotiatorListener(final CapNegotiator.Listener listener) { + this.capNegotiatorListener = listener; + } + /** * Marks you as away on the server. If any user sends a message to you while * marked as away, the the server will send them a message back. diff --git a/src/main/java/com/sorcix/sirc/IrcOutput.java b/src/main/java/com/sorcix/sirc/IrcOutput.java index 010809b..0852437 100644 --- a/src/main/java/com/sorcix/sirc/IrcOutput.java +++ b/src/main/java/com/sorcix/sirc/IrcOutput.java @@ -36,7 +36,7 @@ * * @author Sorcix */ -class IrcOutput extends Thread { +public class IrcOutput extends Thread { /** The IrcConnection. */ private final IrcConnection irc; @@ -171,7 +171,7 @@ protected synchronized void sendNow(final String line) { * @throws IOException If anything goes wrong while sending this * message. */ - protected synchronized void sendNowEx(final IrcPacket packet) throws IOException { + public synchronized void sendNowEx(final IrcPacket packet) throws IOException { this.sendNowEx(packet.getRaw()); } @@ -183,7 +183,7 @@ protected synchronized void sendNowEx(final IrcPacket packet) throws IOException * @throws IOException If anything goes wrong while sending this * message. */ - private synchronized void sendNowEx(String line) throws IOException { + public synchronized void sendNowEx(String line) throws IOException { if (line.length() > (IrcOutput.MAX_LINE_LENGTH - 2)) { line = line.substring(0, IrcOutput.MAX_LINE_LENGTH - 2); } diff --git a/src/main/java/com/sorcix/sirc/IrcPacket.java b/src/main/java/com/sorcix/sirc/IrcPacket.java index 74de8b2..0016d85 100644 --- a/src/main/java/com/sorcix/sirc/IrcPacket.java +++ b/src/main/java/com/sorcix/sirc/IrcPacket.java @@ -27,6 +27,11 @@ */ package com.sorcix.sirc; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + /** * Parses a raw server response into a more readable format. * @@ -39,6 +44,8 @@ */ public final class IrcPacket { + private Date timestamp = null; + /** Arguments separated by a space */ private String arguments = null; /** The numeric server reply. */ @@ -62,7 +69,7 @@ public final class IrcPacket { /** Reply to MOTD. (message of the day) */ protected static final int RPL_MOTD = 372; /** Response to TOPIC with the set topic. */ - protected static final int RPL_TOPIC = 332; + public static final int RPL_TOPIC = 332; /** Termination of an RPL_NAMREPLY list. */ protected static final int RPL_ENDOFNAMES = 366; /** Reply to NAMES (See RFC). */ @@ -72,6 +79,9 @@ public final class IrcPacket { /** CTCP message mark. */ protected static final String CTCP = "\u0001"; + private static DateFormat dateFormat = new SimpleDateFormat( + );//"yyyy-MM-dd'T'HH:mm:ss.SSSX"); + /** * Creates a new IrcPacket using the data from given raw IRC data. * @@ -81,6 +91,23 @@ public final class IrcPacket { * The IrcConnection used to send messages. */ protected IrcPacket(String line, final IrcConnection irc) { + if (line.startsWith("@time=")) { + int endTime = line.indexOf(" "); + if (endTime > 0) { + try { + timestamp = dateFormat.parse( + line.substring(6 /* @time= */, endTime)); + } catch (ParseException e) { + // ignore + timestamp = new Date(); + } + line = line.substring(endTime + 1); + } else { + throw new IllegalArgumentException("malformed IRC line: " + line); + } + } else { + timestamp = new Date(); + } line = IrcColors.remove(line); final int locLineStart = line.indexOf(':') + 1; int locCommand; @@ -232,7 +259,7 @@ public String getPrefix() { * * @return IRC String containing the data in this object. */ - protected String getRaw() { + public String getRaw() { final StringBuffer buffer = new StringBuffer(); if ((this.prefix != null) && (this.prefix.length() > 0)) { @@ -293,4 +320,12 @@ public boolean isCtcp() { public boolean isNumeric() { return this.numeric; } + + public Date getTimestamp() { + return timestamp; + } + + public long getTime() { + return timestamp.getTime(); + } } diff --git a/src/main/java/com/sorcix/sirc/IrcPacketFactory.java b/src/main/java/com/sorcix/sirc/IrcPacketFactory.java index f0a2ba1..705eacf 100644 --- a/src/main/java/com/sorcix/sirc/IrcPacketFactory.java +++ b/src/main/java/com/sorcix/sirc/IrcPacketFactory.java @@ -31,4 +31,15 @@ protected static IrcPacket createUSER(final String username, return new IrcPacket(null, "USER", username + " Sorcix.com *", realname); } + public static IrcPacket createCAPLS() { + return new IrcPacket(null, "CAP", "LS", null); + } + + public static IrcPacket createCAPEND() { + return new IrcPacket(null, "CAP", "END", null); + } + public static IrcPacket createCAPREQ(final String features) { + return new IrcPacket(null, "CAP", "REQ", features); + } + } diff --git a/src/main/java/com/sorcix/sirc/cap/CapNegotiator.java b/src/main/java/com/sorcix/sirc/cap/CapNegotiator.java new file mode 100644 index 0000000..213a1f7 --- /dev/null +++ b/src/main/java/com/sorcix/sirc/cap/CapNegotiator.java @@ -0,0 +1,114 @@ +package com.sorcix.sirc.cap; + +import com.sorcix.sirc.IrcOutput; +import com.sorcix.sirc.IrcPacket; +import com.sorcix.sirc.IrcPacketFactory; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author pfnguyen + */ +public class CapNegotiator { + public interface Listener { + /** + * successful feature request + * @param feature + * @return true if this listener needs to process packets + */ + boolean onNegotiateFeature(CapNegotiator capNegotiator, String feature); + + /** + * unsuccessful feature request + * @param feature + * @return true if this listener should not be ignored (used by CompoundNegotiator) + */ + boolean onNegotiateMissing(CapNegotiator capNegotiator, String feature); + + /** + * list of features available on the server + * @param features + * @return true if this listener will request feature(s) + */ + boolean onNegotiateList(CapNegotiator capNegotiator, String[] features) + throws IOException; + + /** + * while negotiation is active, packets are forwarded to the listener(s) + * @param packet + * @return true while this listener needs to receive more packets + */ + boolean onNegotiate(CapNegotiator capNegotiator, IrcPacket packet) + throws IOException; + } + + private final IrcOutput output; + + public CapNegotiator(IrcOutput output) { + this.output = output; + } + + public void cancel() { + negotiating = false; + listeners.clear(); + } + + public void process(IrcPacket packet) throws IOException { + String[] args = packet.getArgumentsArray(); + boolean isCapCmd = "CAP".equals(packet.getCommand()); + if (isCapCmd && args.length > 1) { + String command = args[1]; + if ("ACK".equals(command)) { + for (Listener l : listeners) { + if (!l.onNegotiateFeature(this, packet.getMessage())) + listeners.remove(l); + } + } else if ("NAK".equals(command)) { + for (Listener l : listeners) { + l.onNegotiateMissing(this, packet.getMessage()); + listeners.remove(l); + } + } else if ("LS".equals(command)) { + negotiating = true; + String[] features = packet.getMessage().split(" "); + for (Listener l : listeners) { + if (!l.onNegotiateList(this, features)) + listeners.remove(l); + } + } + } else { + for (Listener l : listeners) { + if (!l.onNegotiate(this, packet)) + listeners.remove(l); + } + } + if (listeners.size() == 0) { + cancel(); + send(IrcPacketFactory.createCAPEND().getRaw()); + } + } + + public void request(final String feature) throws IOException { + send(IrcPacketFactory.createCAPREQ(feature).getRaw()); + } + public void send(final String rawCommand) throws IOException { + output.sendNowEx(rawCommand); + } + + public void addListener(Listener l) { + listeners.add(l); + } + + public void removeListener(Listener l) { + listeners.remove(l); + } + + public boolean isNegotiating() { + return negotiating; + } + + private boolean negotiating = false; + private List listeners = new CopyOnWriteArrayList(); +} diff --git a/src/main/java/com/sorcix/sirc/cap/CompoundNegotiator.java b/src/main/java/com/sorcix/sirc/cap/CompoundNegotiator.java new file mode 100644 index 0000000..632303c --- /dev/null +++ b/src/main/java/com/sorcix/sirc/cap/CompoundNegotiator.java @@ -0,0 +1,73 @@ +package com.sorcix.sirc.cap; + +import com.sorcix.sirc.IrcPacket; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * @author pfnguyen + */ +public class CompoundNegotiator implements CapNegotiator.Listener { + private List listeners = + new CopyOnWriteArrayList(); + + public CompoundNegotiator(CapNegotiator.Listener... listeners) { + for (CapNegotiator.Listener listener : listeners) { + addListener(listener); + } + } + + public void addListener(CapNegotiator.Listener listener) { + listeners.add(listener); + } + + @Override + public boolean onNegotiateFeature(CapNegotiator capNegotiator, String feature) { + boolean more = false; + for (CapNegotiator.Listener listener : listeners) { + boolean keep = listener.onNegotiateFeature(capNegotiator, feature); + more = more || keep; + if (!keep) + listeners.remove(listener); + } + return more; + } + + @Override + public boolean onNegotiateMissing(CapNegotiator capNegotiator, String feature) { + boolean more = false; + for (CapNegotiator.Listener listener : listeners) { + boolean keep = listener.onNegotiateMissing(capNegotiator, feature); + more = more || keep; + if (!keep) + listeners.remove(listener); + } + return more; + } + + @Override + public boolean onNegotiateList(CapNegotiator capNegotiator, String[] features) throws IOException { + boolean more = false; + for (CapNegotiator.Listener listener : listeners) { + boolean keep = listener.onNegotiateList(capNegotiator, features); + more = more || keep; + if (!keep) + listeners.remove(listener); + } + return more; + } + + @Override + public boolean onNegotiate(CapNegotiator capNegotiator, IrcPacket packet) throws IOException { + boolean more = false; + for (CapNegotiator.Listener listener : listeners) { + boolean keep = listener.onNegotiate(capNegotiator, packet); + more = more || keep; + if (!keep) + listeners.remove(listener); + } + return more; + } +} diff --git a/src/main/java/com/sorcix/sirc/cap/ServerTimeNegotiator.java b/src/main/java/com/sorcix/sirc/cap/ServerTimeNegotiator.java new file mode 100644 index 0000000..e83a267 --- /dev/null +++ b/src/main/java/com/sorcix/sirc/cap/ServerTimeNegotiator.java @@ -0,0 +1,37 @@ +package com.sorcix.sirc.cap; + +import com.sorcix.sirc.IrcPacket; + +import java.io.IOException; + +/** + * @author pfnguyen + */ +public class ServerTimeNegotiator implements CapNegotiator.Listener { + @Override + public boolean onNegotiateFeature(CapNegotiator capNegotiator, String feature) { + return false; + } + + @Override + public boolean onNegotiateMissing(CapNegotiator capNegotiator, String feature) { + return false; + } + + @Override + public boolean onNegotiateList( + CapNegotiator capNegotiator, String[] features) throws IOException { + for (String feature : features) { + if (feature.contains("server-time")) { + capNegotiator.request(feature); + return true; + } + } + return false; + } + + @Override + public boolean onNegotiate(CapNegotiator capNegotiator, IrcPacket packet) throws IOException { + return false; + } +} diff --git a/src/main/java/com/sorcix/sirc/event/BaseEvent.java b/src/main/java/com/sorcix/sirc/event/BaseEvent.java new file mode 100644 index 0000000..4f129dc --- /dev/null +++ b/src/main/java/com/sorcix/sirc/event/BaseEvent.java @@ -0,0 +1,19 @@ +package com.sorcix.sirc.event; + +import com.sorcix.sirc.IrcConnection; +import com.sorcix.sirc.IrcPacket; + +import java.util.Date; + +/** + * @author pfnguyen + */ +public class BaseEvent { + private final Date timestamp; + private final IrcConnection connection; + + public BaseEvent(IrcConnection c, IrcPacket p) { + this.timestamp = p.getTimestamp(); + connection = c; + } +} diff --git a/src/main/java/com/sorcix/sirc/event/MessageEventListener.java b/src/main/java/com/sorcix/sirc/event/MessageEventListener.java new file mode 100644 index 0000000..f3c2249 --- /dev/null +++ b/src/main/java/com/sorcix/sirc/event/MessageEventListener.java @@ -0,0 +1,137 @@ +/* + * MessageListener.java + * + * This file is part of the Sorcix Java IRC Library (sIRC). + * + * Copyright (C) 2008-2010 Vic Demuzere http://sorcix.com + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.sorcix.sirc.event; + +import com.sorcix.sirc.Channel; +import com.sorcix.sirc.IrcConnection; +import com.sorcix.sirc.IrcPacket; +import com.sorcix.sirc.User; + +/** + * Notified of new IRC messages. + */ +public interface MessageEventListener { + + public static class Action extends BaseEvent { + public final User sender; + public final Channel target; + public final String action; + + public Action(IrcConnection c, IrcPacket p) { + super(c, p); + action = p.getMessage().substring(7); + if (Channel.CHANNEL_PREFIX.indexOf(p.getArguments().charAt(0)) >= 0) { + // to channel + target = c.getState().getChannel(p.getArguments()); + sender = target.updateUser(p.getSender(), true); + } else { + // to user + target = null; + sender = p.getSender(); + } + } + } + + public static class CtcpReply extends BaseEvent { + public final String command; + public final User sender; + public final String message; + + public CtcpReply(IrcConnection c, IrcPacket p) { + super(c, p); + final int cmdPos = p.getMessage().indexOf(' '); + command = p.getMessage().substring(0, cmdPos); + final String args = p.getMessage().substring(cmdPos + 1); + sender = p.getSender(); + message = args; + } + } + + public static class Message extends BaseEvent { + public final User sender; + public final Channel target; + public final String message; + public Message(IrcConnection c, IrcPacket p) { + super(c, p); + message = p.getMessage(); + if (Channel.CHANNEL_PREFIX.indexOf( + p.getArguments().charAt(0)) >= 0) { + target = c.getState().getChannel(p.getArguments()); + sender = target.updateUser(p.getSender(), true); + } else { + target = null; + sender = p.getSender(); + } + } + } + + public static class Notice extends BaseEvent { + public final User sender; + public final Channel target; + public final String message; + public Notice(IrcConnection c, IrcPacket p) { + super(c, p); + message = p.getMessage(); + if (Channel.CHANNEL_PREFIX.indexOf( + p.getArguments().charAt(0)) >= 0) { + target = c.getState().getChannel(p.getArguments()); + sender = target.updateUser(p.getSender(), true); + } else { + target = null; + sender = p.getSender(); + } + } + } + + /** + * Received an action in a channel. + */ + void onAction(Action action); + + /** + * Received a CTCP reply. Note that this event is only fired when + * receiving CTCP replies supported by sIRC. If you can't send a + * CTCP request, you won't get the reply. + */ + void onCtcpReply(CtcpReply reply); + + /** + * Received a message in a channel. + */ + void onMessage(Message message); + + /** + * Received a private notice. + */ + void onNotice(Notice notice); + + /** + * Received a private message. + */ + void onPrivateMessage(Message message); +} \ No newline at end of file diff --git a/src/main/java/com/sorcix/sirc/event/ServerEventListener.java b/src/main/java/com/sorcix/sirc/event/ServerEventListener.java new file mode 100644 index 0000000..30ced5c --- /dev/null +++ b/src/main/java/com/sorcix/sirc/event/ServerEventListener.java @@ -0,0 +1,245 @@ +/* + * ServerListener.java + * + * This file is part of the Sorcix Java IRC Library (sIRC). + * + * Copyright (C) 2008-2010 Vic Demuzere http://sorcix.com + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.sorcix.sirc.event; + +import com.sorcix.sirc.Channel; +import com.sorcix.sirc.IrcConnection; +import com.sorcix.sirc.IrcPacket; +import com.sorcix.sirc.User; + +public interface ServerEventListener { + + public static class Invite extends BaseEvent { + public final User sender; + public final User target; + public final Channel channel; + + public Invite(IrcConnection c, IrcPacket p) { + super(c, p); + sender = p.getSender(); + target = new User(p.getArgumentsArray()[0], c); + channel = c.createChannel(p.getArgumentsArray()[1]); + } + } + + public static class Join extends BaseEvent { + public final Channel channel; + public final User sender; + + public Join(IrcConnection c, IrcPacket p) { + super(c, p); + String channel; + if (p.hasMessage()) { + channel = p.getMessage(); + } else { + channel = p.getArguments(); + } + // someone joined a channel + if (p.getSender().isUs()) { + // if the sender joining the channel is the client + // we need to add it to the channel list. + c.getState().addChannel(new Channel(channel, c, true)); + } else { + // add sender to channel list. + c.getState().getChannel(channel).addUser(p.getSender()); + } + this.channel = c.getState().getChannel(channel); + this.sender = p.getSender(); + } + } + + public static class Kick extends BaseEvent { + public final Channel channel; + public final User sender; + public final User target; + public final String message; + + public Kick(IrcConnection c, IrcPacket p) { + super(c, p); + + final String[] data = p.getArgumentsArray(); + final User kicked = new User(data[1], c); + final Channel channel = c.getState().getChannel(data[0]); + if (kicked.isUs()) { + // if the user leaving the channel is the client + // we need to remove it from the channel list + c.getState().removeChannel(data[0]); + } else { + // remove user from channel list. + channel.removeUser(kicked); + } + this.channel = channel; + this.sender = p.getSender(); + this.target = kicked; + this.message = p.getMessage(); + } + } + + public static class Mode extends BaseEvent { + public final Channel channel; + public final User sender; + public final String mode; + public Mode(IrcConnection c, IrcPacket p) { + super(c, p); + channel = c.getState().getChannel(p.getArgumentsArray()[0]); + sender = p.getSender(); + mode = p.getArguments().substring( + p.getArgumentsArray()[0].length() + 1); + } + } + + public static class Nick extends BaseEvent { + public final User oldUser; + public final User newUser; + public Nick(IrcConnection c, IrcPacket p) { + super(c, p); + newUser = new User( + p.hasMessage() ? p.getMessage() : p.getArguments(), c); + oldUser = p.getSender(); + } + + } + + public static class Part extends BaseEvent { + public final User sender; + public final Channel channel; + public final String message; + + public Part(IrcConnection c, IrcPacket p) { + super(c, p); + channel = c.getState().getChannel(p.getArguments()); + sender = p.getSender(); + message = p.getMessage(); + } + } + + public static class Quit extends BaseEvent { + public final User sender; + public final String message; + + public Quit(IrcConnection c, IrcPacket p) { + super(c, p); + sender = p.getSender(); + message = p.getMessage(); + } + } + + public static class Topic extends BaseEvent { + public final User sender; + public final Channel channel; + public final String topic; + public Topic(IrcConnection c, IrcPacket p) { + super(c, p); + + if ("TOPIC".equals(p.getCommand())) { + final Channel chan = c.getState().getChannel(p.getArguments()); + channel = chan; + sender = chan.updateUser(p.getSender(), false); + topic = p.getMessage(); + } else if (p.getNumericCommand() == IrcPacket.RPL_TOPIC) { + channel = c.getState().getChannel(p.getArgumentsArray()[1]); + sender = null; + topic = p.getMessage(); + } else { + channel = null; + sender = null; + topic = null; + } + } + } + + /** + * Someone (possibly us) was invited into a channel. + */ + void onInvite(Invite invite); + + /** + * Someone (possibly us) joined a channel. + */ + void onJoin(Join join); + + /** + * Someone (possibly us) was kicked from a channel. + *

+ * Note: This method does NOT return a shared + * user object. That means that it isn't possible to retrieve the + * user prefix (or any modes). + *

+ */ + void onKick(Kick kick); + + /** + * Someone (possibly us) changed a channel mode. + */ + void onMode(Mode mode); + + /** + * The server sent the Message of the Day. + * + * @param irc The {@link com.sorcix.sirc.IrcConnection} receiving this event. + * @param motd The message of the day. + * @see com.sorcix.sirc.IrcConnection#askMotd() + * @since 1.0.2 + */ + void onMotd(IrcConnection irc, String motd); + + /** + * Someone (possibly us) changed his nickname. Note that the + * {@code oldUser} can not be used to send messages, as that + * nickname no longer exists. + *

+ * Note: This method does NOT return a shared + * sender object. That means that it isn't possible to retrieve the + * sender prefix (or any modes). + *

+ */ + void onNick(Nick nick); + + /** + * Someone (possibly us) left a channel. + *

+ * Note: This method does NOT return a shared + * sender object. That means that it isn't possible to retrieve the + * sender prefix (or any modes). + *

+ */ + void onPart(Part part); + + /** + * Someone quit the IRC server. + */ + void onQuit(Quit quit); + + /** + * Someone (possibly us) changed the topic of a channel, or we + * joined a new channel and discovered the topic. The {@code + * sender} will be {@code null} when we discovered the topic after + * joining. + */ + void onTopic(Topic topic); +} diff --git a/src/test/java/com/sorcix/sirc/IrcPacketTest.java b/src/test/java/com/sorcix/sirc/IrcPacketTest.java new file mode 100644 index 0000000..7ea5a7b --- /dev/null +++ b/src/test/java/com/sorcix/sirc/IrcPacketTest.java @@ -0,0 +1,67 @@ +package com.sorcix.sirc; + +import static org.mockito.Matchers.matches; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.junit.Assert.*; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class IrcPacketTest { + @Mock + IrcConnection connection; + + @Test + public void capLs() { + IrcPacket p = new IrcPacket(":leguin.freenode.net CAP * LS :" + + "account-notify extended-join identify-msg multi-prefix sasl", + connection); + assertEquals("CAP", p.getCommand()); + assertEquals( + "account-notify extended-join identify-msg multi-prefix sasl", + p.getMessage()); + assertEquals("* LS", p.getArguments()); + assertEquals("LS", p.getArgumentsArray()[1]); + } + + @Test + public void capReqMultiPrefix() { + IrcPacket p = new IrcPacket( + ":irc.colosolutions.net CAP * ACK :multi-prefix", connection); + assertEquals("CAP", p.getCommand()); + assertEquals("multi-prefix", p.getMessage()); + assertEquals("* ACK", p.getArguments()); + assertEquals("ACK", p.getArgumentsArray()[1]); + } + + @Test + public void capNak() { + IrcPacket p = new IrcPacket( + ":irc.znc.in CAP unknown-nick NAK :server-time-iso", connection); + assertEquals("CAP", p.getCommand()); + assertEquals("server-time-iso", p.getMessage()); + assertEquals("unknown-nick NAK", p.getArguments()); + assertEquals("NAK", p.getArgumentsArray()[1]); + } + + @Test + public void createCapLs() { + IrcPacket p = IrcPacketFactory.createCAPLS(); + assertEquals("CAP LS", p.getRaw()); + } + + @Test + public void welcomeNumeric() { + IrcPacket p = new IrcPacket("@time=2014-09-15T15:33:11.232Z :" + + "irc.choopa.net 001 pfn :" + + "Welcome to the EFNet Internet Relay Chat Network pfn", + connection); + assertTrue(p.isNumeric()); + assertEquals(1, p.getNumericCommand()); + } + +} From 17ebbf1217e8c125d8f061935cb84fb56db0adc7 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Wed, 5 Nov 2014 16:47:27 -0800 Subject: [PATCH 05/22] Initial hookup of new event listeners --- .../java/com/sorcix/sirc/IrcConnection.java | 16 +++++++ src/main/java/com/sorcix/sirc/IrcParser.java | 48 +++++++++++++++++++ .../sirc/event/ServerEventListener.java | 10 ---- 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/sorcix/sirc/IrcConnection.java b/src/main/java/com/sorcix/sirc/IrcConnection.java index cf90f4d..c5f4b3d 100644 --- a/src/main/java/com/sorcix/sirc/IrcConnection.java +++ b/src/main/java/com/sorcix/sirc/IrcConnection.java @@ -28,6 +28,8 @@ package com.sorcix.sirc; import com.sorcix.sirc.cap.CapNegotiator; +import com.sorcix.sirc.event.MessageEventListener; +import com.sorcix.sirc.event.ServerEventListener; import javax.net.SocketFactory; import javax.net.ssl.SSLContext; @@ -70,6 +72,10 @@ public class IrcConnection { private int messageDelay = 100; /** Message listeners. */ private final List messageListeners; + + private final List messageEventListeners; + private final List serverEventListeners; + /** Mode listeners. */ private final List modeListeners; /** Connection OutputStream thread. */ @@ -137,6 +143,8 @@ public IrcConnection(final String server, final int port) { public IrcConnection(final String server, final int port, final String password) { this.server = new IrcServer(server, port, password, false); + this.serverEventListeners = new Vector(4); + this.messageEventListeners = new Vector(4); this.serverListeners = new Vector(4); this.messageListeners = new Vector(4); this.modeListeners = new Vector(2); @@ -642,6 +650,14 @@ protected Iterator getServerListeners() { return this.serverListeners.iterator(); } + protected List getServerEventListeners() { + return this.serverEventListeners; + } + + protected List getMessageEventListeners() { + return this.messageEventListeners; + } + /** * Gives the port number this {@code IrcConnection} is using to connect. * diff --git a/src/main/java/com/sorcix/sirc/IrcParser.java b/src/main/java/com/sorcix/sirc/IrcParser.java index 5eed5da..c6ecf9a 100644 --- a/src/main/java/com/sorcix/sirc/IrcParser.java +++ b/src/main/java/com/sorcix/sirc/IrcParser.java @@ -27,6 +27,9 @@ */ package com.sorcix.sirc; +import com.sorcix.sirc.event.MessageEventListener; +import com.sorcix.sirc.event.ServerEventListener; + import java.util.Date; import java.util.Iterator; @@ -51,6 +54,9 @@ protected void parseCommand(final IrcConnection irc, final IrcPacket line) { if (line.isCtcp()) { // reply to CTCP commands if (line.getMessage().startsWith("ACTION ")) { + for (MessageEventListener l : irc.getMessageEventListeners()) { + l.onAction(new MessageEventListener.Action(irc, line)); + } if (Channel.CHANNEL_PREFIX.indexOf(line.getArguments().charAt(0)) >= 0) { // to channel final Channel chan = irc.getState().getChannel(line.getArguments()); @@ -88,6 +94,9 @@ protected void parseCommand(final IrcConnection irc, final IrcPacket line) { } else if (line.getArguments().startsWith("#") || line.getArguments().startsWith("&")) { // to channel final Channel chan = irc.getState().getChannel(line.getArguments()); + for (MessageEventListener l : irc.getMessageEventListeners()) { + l.onMessage(new MessageEventListener.Message(irc, line)); + } for (final Iterator it = irc.getMessageListeners(); it.hasNext();) { it.next().onMessage(irc, chan.updateUser(line.getSender(), true), chan, line.getMessage()); } @@ -96,6 +105,9 @@ protected void parseCommand(final IrcConnection irc, final IrcPacket line) { for (final Iterator it = irc.getMessageListeners(); it.hasNext();) { it.next().onPrivateMessage(irc, line.getSender(), line.getMessage()); } + for (MessageEventListener l : irc.getMessageEventListeners()) { + l.onPrivateMessage(new MessageEventListener.Message(irc, line)); + } } } else if (line.getCommand().equals("NOTICE") && (line.getArguments() != null)) { if (line.isCtcp()) { @@ -108,17 +120,26 @@ protected void parseCommand(final IrcConnection irc, final IrcPacket line) { it.next().onCtcpReply(irc, line.getSender(), command, args); } } + for (MessageEventListener l : irc.getMessageEventListeners()) { + l.onCtcpReply(new MessageEventListener.CtcpReply(irc, line)); + } } else if (Channel.CHANNEL_PREFIX.indexOf(line.getArguments().charAt(0)) >= 0) { // to channel final Channel chan = irc.getState().getChannel(line.getArguments()); for (final Iterator it = irc.getMessageListeners(); it.hasNext();) { it.next().onNotice(irc, chan.updateUser(line.getSender(), true), chan, line.getMessage()); } + for (MessageEventListener l : irc.getMessageEventListeners()) { + l.onNotice(new MessageEventListener.Notice(irc, line)); + } } else { // to user for (final Iterator it = irc.getMessageListeners(); it.hasNext();) { it.next().onNotice(irc, line.getSender(), line.getMessage()); } + for (MessageEventListener l : irc.getMessageEventListeners()) { + l.onNotice(new MessageEventListener.Notice(irc, line)); + } } } else if (line.getCommand().equals("JOIN")) { // some server seem to send the joined channel as message, @@ -141,6 +162,9 @@ protected void parseCommand(final IrcConnection irc, final IrcPacket line) { for (final Iterator it = irc.getServerListeners(); it.hasNext();) { it.next().onJoin(irc, irc.getState().getChannel(channel), line.getSender()); } + for (ServerEventListener l : irc.getServerEventListeners()) { + l.onJoin(new ServerEventListener.Join(irc, line)); + } } else if (line.getCommand().equals("PART")) { // someone left a channel if (line.getSender().isUs()) { @@ -156,12 +180,18 @@ protected void parseCommand(final IrcConnection irc, final IrcPacket line) { for (final Iterator it = irc.getServerListeners(); it.hasNext();) { it.next().onPart(irc, irc.getState().getChannel(line.getArguments()), line.getSender(), line.getMessage()); } + for (ServerEventListener l : irc.getServerEventListeners()) { + l.onPart(new ServerEventListener.Part(irc, line)); + } } else if (line.getCommand().equals("QUIT")) { // someone quit the IRC server final User quitter = line.getSender(); for (final Iterator it = irc.getServerListeners(); it.hasNext();) { it.next().onQuit(irc, quitter, line.getMessage()); } + for (ServerEventListener l : irc.getServerEventListeners()) { + l.onQuit(new ServerEventListener.Quit(irc, line)); + } for (final Iterator it = irc.getState().getChannels(); it.hasNext();) { final Channel channel = it.next(); if (channel.hasUser(quitter)) { @@ -184,6 +214,9 @@ protected void parseCommand(final IrcConnection irc, final IrcPacket line) { for (final Iterator it = irc.getServerListeners(); it.hasNext();) { it.next().onKick(irc, channel, line.getSender(), kicked, line.getMessage()); } + for (ServerEventListener l : irc.getServerEventListeners()) { + l.onKick(new ServerEventListener.Kick(irc, line)); + } } else if (line.getCommand().equals("MODE")) { this.parseMode(irc, line); } else if (line.getCommand().equals("TOPIC")) { @@ -192,6 +225,9 @@ protected void parseCommand(final IrcConnection irc, final IrcPacket line) { final Channel chan = irc.getState().getChannel(line.getArguments()); it.next().onTopic(irc, chan, chan.updateUser(line.getSender(), false), line.getMessage()); } + for (ServerEventListener l : irc.getServerEventListeners()) { + l.onTopic(new ServerEventListener.Topic(irc, line)); + } } else if (line.getCommand().equals("NICK")) { User newUser; if (line.hasMessage()) { @@ -210,6 +246,9 @@ protected void parseCommand(final IrcConnection irc, final IrcPacket line) { for (final Iterator it = irc.getServerListeners(); it.hasNext();) { it.next().onNick(irc, line.getSender(), newUser); } + for (ServerEventListener l : irc.getServerEventListeners()) { + l.onNick(new ServerEventListener.Nick(irc, line)); + } } else if (line.getCommand().equals("INVITE")) { // someone was invited final String[] args = line.getArgumentsArray(); @@ -218,6 +257,9 @@ protected void parseCommand(final IrcConnection irc, final IrcPacket line) { for (final Iterator it = irc.getServerListeners(); it.hasNext();) { it.next().onInvite(irc, line.getSender(), new User(args[0], irc), channel); } + for (ServerEventListener l : irc.getServerEventListeners()) { + l.onInvite(new ServerEventListener.Invite(irc, line)); + } } } else { if (irc.getAdvancedListener() != null) { @@ -239,6 +281,9 @@ private void parseMode(final IrcConnection irc, final IrcPacket line) { for (final Iterator it = irc.getServerListeners(); it.hasNext();) { it.next().onMode(irc, irc.getState().getChannel(args[0]), line.getSender(), line.getArguments().substring(args[0].length() + 1)); } + for (ServerEventListener l : irc.getServerEventListeners()) { + l.onMode(new ServerEventListener.Mode(irc, line)); + } if ((args.length >= 3)) { final Channel channel = irc.getState().getChannel(args[0]); final String mode = args[1]; @@ -327,6 +372,9 @@ protected void parseNumeric(final IrcConnection irc, final IrcPacket line) { for (final Iterator it = irc.getServerListeners(); it.hasNext();) { it.next().onTopic(irc, irc.getState().getChannel(line.getArgumentsArray()[1]), null, line.getMessage()); } + for (ServerEventListener l : irc.getServerEventListeners()) { + l.onTopic(new ServerEventListener.Topic(irc, line)); + } break; case IrcPacket.RPL_NAMREPLY: final String[] arguments = line.getArgumentsArray(); diff --git a/src/main/java/com/sorcix/sirc/event/ServerEventListener.java b/src/main/java/com/sorcix/sirc/event/ServerEventListener.java index 30ced5c..586da79 100644 --- a/src/main/java/com/sorcix/sirc/event/ServerEventListener.java +++ b/src/main/java/com/sorcix/sirc/event/ServerEventListener.java @@ -198,16 +198,6 @@ public Topic(IrcConnection c, IrcPacket p) { */ void onMode(Mode mode); - /** - * The server sent the Message of the Day. - * - * @param irc The {@link com.sorcix.sirc.IrcConnection} receiving this event. - * @param motd The message of the day. - * @see com.sorcix.sirc.IrcConnection#askMotd() - * @since 1.0.2 - */ - void onMotd(IrcConnection irc, String motd); - /** * Someone (possibly us) changed his nickname. Note that the * {@code oldUser} can not be used to send messages, as that From c8699d8402715393bfa5627ed7fef9f61ce362f2 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Wed, 5 Nov 2014 20:10:07 -0800 Subject: [PATCH 06/22] add listener registration, uncomment date format --- .../java/com/sorcix/sirc/IrcConnection.java | 26 ++++++++++++++++++- src/main/java/com/sorcix/sirc/IrcPacket.java | 2 +- .../java/com/sorcix/sirc/event/BaseEvent.java | 4 +-- .../java/com/sorcix/sirc/IrcPacketTest.java | 8 ++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/sorcix/sirc/IrcConnection.java b/src/main/java/com/sorcix/sirc/IrcConnection.java index c5f4b3d..a654654 100644 --- a/src/main/java/com/sorcix/sirc/IrcConnection.java +++ b/src/main/java/com/sorcix/sirc/IrcConnection.java @@ -176,6 +176,18 @@ public void addMessageListener(final MessageListener listener) { } } + /** + * Adds a message listener to this IrcConnection. + * + * @param listener + * The message listener to add. + */ + public void addMessageEventListener(final MessageEventListener listener) { + if ((listener != null) && !this.messageEventListeners.contains(listener)) { + this.messageEventListeners.add(listener); + } + } + /** * Adds a mode listener to this IrcConnection. Note that adding mode * listeners will cause sIRC to check every incoming mode change for @@ -204,7 +216,19 @@ public void addServerListener(final ServerListener listener) { } } - /** + /** + * Adds a server listener to this IrcConnection. + * + * @param listener + * The server listener to add. + */ + public void addServerEventListener(final ServerEventListener listener) { + if ((listener != null) && !this.serverEventListeners.contains(listener)) { + this.serverEventListeners.add(listener); + } + } + + /** * Add and load a service. {@code IrcConnection} will call the * {@link SIRCService#load(IrcConnection)} method of this * {@code SIRCService} after adding it to the service list. diff --git a/src/main/java/com/sorcix/sirc/IrcPacket.java b/src/main/java/com/sorcix/sirc/IrcPacket.java index 0016d85..a066f20 100644 --- a/src/main/java/com/sorcix/sirc/IrcPacket.java +++ b/src/main/java/com/sorcix/sirc/IrcPacket.java @@ -80,7 +80,7 @@ public final class IrcPacket { protected static final String CTCP = "\u0001"; private static DateFormat dateFormat = new SimpleDateFormat( - );//"yyyy-MM-dd'T'HH:mm:ss.SSSX"); + "yyyy-MM-dd'T'HH:mm:ss.SSSX"); /** * Creates a new IrcPacket using the data from given raw IRC data. diff --git a/src/main/java/com/sorcix/sirc/event/BaseEvent.java b/src/main/java/com/sorcix/sirc/event/BaseEvent.java index 4f129dc..fbdc24f 100644 --- a/src/main/java/com/sorcix/sirc/event/BaseEvent.java +++ b/src/main/java/com/sorcix/sirc/event/BaseEvent.java @@ -9,8 +9,8 @@ * @author pfnguyen */ public class BaseEvent { - private final Date timestamp; - private final IrcConnection connection; + public final Date timestamp; + public final IrcConnection connection; public BaseEvent(IrcConnection c, IrcPacket p) { this.timestamp = p.getTimestamp(); diff --git a/src/test/java/com/sorcix/sirc/IrcPacketTest.java b/src/test/java/com/sorcix/sirc/IrcPacketTest.java index 7ea5a7b..6804a27 100644 --- a/src/test/java/com/sorcix/sirc/IrcPacketTest.java +++ b/src/test/java/com/sorcix/sirc/IrcPacketTest.java @@ -10,6 +10,9 @@ import static org.junit.Assert.*; import org.mockito.runners.MockitoJUnitRunner; +import java.util.Calendar; +import java.util.Date; + @RunWith(MockitoJUnitRunner.class) public class IrcPacketTest { @Mock @@ -60,6 +63,11 @@ public void welcomeNumeric() { "irc.choopa.net 001 pfn :" + "Welcome to the EFNet Internet Relay Chat Network pfn", connection); + Date ts = p.getTimestamp(); + assertEquals(1410795191232l, p.getTime()); + Calendar c = Calendar.getInstance(); + c.setTime(ts); + assertEquals(8, c.get(Calendar.MONTH)); assertTrue(p.isNumeric()); assertEquals(1, p.getNumericCommand()); } From 7e1dd8881e5ef2cf3fc3ab30a9dda11222d66e10 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Thu, 6 Nov 2014 14:39:02 -0800 Subject: [PATCH 07/22] android workaround --- src/main/java/com/sorcix/sirc/IrcPacket.java | 19 +++++++++++++++++-- .../java/com/sorcix/sirc/IrcPacketTest.java | 3 +++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sorcix/sirc/IrcPacket.java b/src/main/java/com/sorcix/sirc/IrcPacket.java index a066f20..a37cabd 100644 --- a/src/main/java/com/sorcix/sirc/IrcPacket.java +++ b/src/main/java/com/sorcix/sirc/IrcPacket.java @@ -31,6 +31,7 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.TimeZone; /** * Parses a raw server response into a more readable format. @@ -79,8 +80,22 @@ public final class IrcPacket { /** CTCP message mark. */ protected static final String CTCP = "\u0001"; - private static DateFormat dateFormat = new SimpleDateFormat( - "yyyy-MM-dd'T'HH:mm:ss.SSSX"); + private final static DateFormat dateFormat; + + static { + + SimpleDateFormat sdf = null; + try { + sdf = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSSX"); + } catch (IllegalArgumentException e) { + // android fallback, it does not support 'X' + sdf = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + } + dateFormat = sdf; + } /** * Creates a new IrcPacket using the data from given raw IRC data. diff --git a/src/test/java/com/sorcix/sirc/IrcPacketTest.java b/src/test/java/com/sorcix/sirc/IrcPacketTest.java index 6804a27..0ab6d09 100644 --- a/src/test/java/com/sorcix/sirc/IrcPacketTest.java +++ b/src/test/java/com/sorcix/sirc/IrcPacketTest.java @@ -64,10 +64,13 @@ public void welcomeNumeric() { "Welcome to the EFNet Internet Relay Chat Network pfn", connection); Date ts = p.getTimestamp(); + assertEquals(new Date(1410795191232l), p.getTimestamp()); assertEquals(1410795191232l, p.getTime()); Calendar c = Calendar.getInstance(); c.setTime(ts); assertEquals(8, c.get(Calendar.MONTH)); + // only works in GMT-7 +// assertEquals(8, c.get(Calendar.HOUR)); assertTrue(p.isNumeric()); assertEquals(1, p.getNumericCommand()); } From 7217cec3bba7cd59252b52ae77f4b42e4a6a050c Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Fri, 7 Nov 2014 08:10:49 -0800 Subject: [PATCH 08/22] synchronize date format --- src/main/java/com/sorcix/sirc/IrcPacket.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sorcix/sirc/IrcPacket.java b/src/main/java/com/sorcix/sirc/IrcPacket.java index a37cabd..4f65ae9 100644 --- a/src/main/java/com/sorcix/sirc/IrcPacket.java +++ b/src/main/java/com/sorcix/sirc/IrcPacket.java @@ -110,8 +110,10 @@ protected IrcPacket(String line, final IrcConnection irc) { int endTime = line.indexOf(" "); if (endTime > 0) { try { - timestamp = dateFormat.parse( - line.substring(6 /* @time= */, endTime)); + synchronized (dateFormat) { + timestamp = dateFormat.parse( + line.substring(6 /* @time= */, endTime)); + } } catch (ParseException e) { // ignore timestamp = new Date(); From e1ef2997c2ea95aebf77650089b06e641ded7f16 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Tue, 9 Dec 2014 17:10:25 -0800 Subject: [PATCH 09/22] Add remove event listeners --- src/main/java/com/sorcix/sirc/IrcConnection.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/sorcix/sirc/IrcConnection.java b/src/main/java/com/sorcix/sirc/IrcConnection.java index a654654..472d09f 100644 --- a/src/main/java/com/sorcix/sirc/IrcConnection.java +++ b/src/main/java/com/sorcix/sirc/IrcConnection.java @@ -812,6 +812,18 @@ public void removeServerListener(final ServerListener listener) { } } + public void removeServerEventListener(final ServerEventListener listener) { + if ((listener != null) && this.serverEventListeners.contains(listener)) { + this.serverEventListeners.remove(listener); + } + } + + public void removeMessageEventListener(final MessageEventListener listener) { + if ((listener != null) && this.messageEventListeners.contains(listener)) { + this.messageEventListeners.remove(listener); + } + } + /** * Remove a service. {@code IrcConnection} will call the * {@link SIRCService#unload(IrcConnection)} method of this From a6b5a31403ed8cfe59a60c2633f06095592f8308 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Tue, 9 Dec 2014 21:04:20 -0800 Subject: [PATCH 10/22] Fixes for capability negotiation do not redundantly process CAP messages (check isNegotiating first) allow IrcPacket to read malformed lines (use for raw processing) trim the feature name when passing to capability negotiators --- src/main/java/com/sorcix/sirc/IrcConnection.java | 2 +- src/main/java/com/sorcix/sirc/IrcPacket.java | 4 ++++ src/main/java/com/sorcix/sirc/cap/CapNegotiator.java | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/sorcix/sirc/IrcConnection.java b/src/main/java/com/sorcix/sirc/IrcConnection.java index 472d09f..1e4bdb6 100644 --- a/src/main/java/com/sorcix/sirc/IrcConnection.java +++ b/src/main/java/com/sorcix/sirc/IrcConnection.java @@ -458,7 +458,7 @@ public void connect(Socket sock) throws UnknownHostException, IOException, NickN throw new PasswordException("Invalid password"); } // break; unnecessary due to throw } - } else if ("CAP".equals(decoder.getCommand())) { + } else if ("CAP".equals(decoder.getCommand()) && !capNegotiator.isNegotiating()) { capNegotiator.process(decoder); } if (line.startsWith("PING ")) { diff --git a/src/main/java/com/sorcix/sirc/IrcPacket.java b/src/main/java/com/sorcix/sirc/IrcPacket.java index 4f65ae9..dcb4e73 100644 --- a/src/main/java/com/sorcix/sirc/IrcPacket.java +++ b/src/main/java/com/sorcix/sirc/IrcPacket.java @@ -46,6 +46,7 @@ public final class IrcPacket { private Date timestamp = null; + private String rawLine = null; /** Arguments separated by a space */ private String arguments = null; @@ -106,6 +107,7 @@ public final class IrcPacket { * The IrcConnection used to send messages. */ protected IrcPacket(String line, final IrcConnection irc) { + rawLine = line; if (line.startsWith("@time=")) { int endTime = line.indexOf(" "); if (endTime > 0) { @@ -140,6 +142,7 @@ protected IrcPacket(String line, final IrcConnection irc) { } // space between command and receiver final int locArgs = line.indexOf(' ', locCommand + 1); + if (locArgs < 0) return; // rawLine only packet // retrieve command this.command = line.substring(locCommand + 1, locArgs); // colon between arguments and message @@ -277,6 +280,7 @@ public String getPrefix() { * @return IRC String containing the data in this object. */ public String getRaw() { + if (rawLine != null) return rawLine; final StringBuffer buffer = new StringBuffer(); if ((this.prefix != null) && (this.prefix.length() > 0)) { diff --git a/src/main/java/com/sorcix/sirc/cap/CapNegotiator.java b/src/main/java/com/sorcix/sirc/cap/CapNegotiator.java index 213a1f7..8f1c79b 100644 --- a/src/main/java/com/sorcix/sirc/cap/CapNegotiator.java +++ b/src/main/java/com/sorcix/sirc/cap/CapNegotiator.java @@ -62,12 +62,12 @@ public void process(IrcPacket packet) throws IOException { String command = args[1]; if ("ACK".equals(command)) { for (Listener l : listeners) { - if (!l.onNegotiateFeature(this, packet.getMessage())) + if (!l.onNegotiateFeature(this, packet.getMessage().trim())) listeners.remove(l); } } else if ("NAK".equals(command)) { for (Listener l : listeners) { - l.onNegotiateMissing(this, packet.getMessage()); + l.onNegotiateMissing(this, packet.getMessage().trim()); listeners.remove(l); } } else if ("LS".equals(command)) { From ccf93d1f3435b1626bc4554db79ab78897fc5dd3 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Tue, 20 Oct 2015 09:51:08 -0700 Subject: [PATCH 11/22] 1.1.6-pfn --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 323a9ce..f92b6c4 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ crossPaths := false organization := "com.hanhuy" -version := "1.1.6-SNAPSHOT" +version := "1.1.6-pfn" // sonatype publishing options follow publishMavenStyle := true From 704beb0065cde7c471341ee9430c4ef2c1c921fa Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Mon, 27 Oct 2014 17:47:28 -0600 Subject: [PATCH 12/22] Some small performance and thread safety improvements --- src/main/java/com/sorcix/sirc/Channel.java | 15 +++++++-------- src/main/java/com/sorcix/sirc/IrcQueue.java | 6 +++--- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/sorcix/sirc/Channel.java b/src/main/java/com/sorcix/sirc/Channel.java index 3297d4f..da04b5e 100644 --- a/src/main/java/com/sorcix/sirc/Channel.java +++ b/src/main/java/com/sorcix/sirc/Channel.java @@ -72,9 +72,9 @@ public Channel(final String name, final IrcConnection irc, final boolean global) * * @param user The user to add. */ - public void addUser(final User user) { - if ((this.users != null) && !this.users.containsKey(user.getNickLower())) { - this.users.put(user.getNickLower(), user); + protected void addUser(final User user) { + if (this.users != null) { + this.users.putIfAbsent(user.getNickLower(), user); } } @@ -358,8 +358,8 @@ public void removeOperator(final User user) { * * @param user The user to remove. */ - public void removeUser(final User user) { - if ((this.users != null) && this.users.containsKey(user.getNickLower())) { + protected void removeUser(final User user) { + if (this.users != null) { this.users.remove(user.getNickLower()); } } @@ -380,9 +380,8 @@ public void removeVoice(final User user) { * @param neww The new nickname. */ protected void renameUser(final String old, final String neww) { - if ((this.users != null) && this.users.containsKey(old)) { - final User user = this.users.get(old); - this.users.remove(old); + if (this.users != null) { + final User user = this.users.remove(old); user.setNick(neww); this.users.put(user.getNickLower(), user); } diff --git a/src/main/java/com/sorcix/sirc/IrcQueue.java b/src/main/java/com/sorcix/sirc/IrcQueue.java index b6ae7a6..c4ab965 100644 --- a/src/main/java/com/sorcix/sirc/IrcQueue.java +++ b/src/main/java/com/sorcix/sirc/IrcQueue.java @@ -27,7 +27,7 @@ */ package com.sorcix.sirc; -import java.util.LinkedList; +import java.util.ArrayDeque; /** * Outgoing message queue. @@ -37,13 +37,13 @@ final class IrcQueue { /** Message Queue. */ - private final LinkedList queue; + private final ArrayDeque queue; /** * Creates a new outgoing message queue. */ protected IrcQueue() { - this.queue = new LinkedList(); + this.queue = new ArrayDeque(8); } /** From 0ec33920cbea07291b28c74747359ea2c1d1fc67 Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Mon, 27 Oct 2014 17:52:01 -0600 Subject: [PATCH 13/22] Fix for condition where user is not in the map --- src/main/java/com/sorcix/sirc/Channel.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sorcix/sirc/Channel.java b/src/main/java/com/sorcix/sirc/Channel.java index da04b5e..6988a95 100644 --- a/src/main/java/com/sorcix/sirc/Channel.java +++ b/src/main/java/com/sorcix/sirc/Channel.java @@ -382,8 +382,10 @@ public void removeVoice(final User user) { protected void renameUser(final String old, final String neww) { if (this.users != null) { final User user = this.users.remove(old); - user.setNick(neww); - this.users.put(user.getNickLower(), user); + if (user != null) { + user.setNick(neww); + this.users.put(user.getNickLower(), user); + } } } From 0d4bf9ee8a1f78c09ed61ba4b0c1892e7fd1d883 Mon Sep 17 00:00:00 2001 From: Mike Jensen Date: Mon, 27 Oct 2014 17:54:19 -0600 Subject: [PATCH 14/22] Spacing fix (Not used to working in tabs) --- src/main/java/com/sorcix/sirc/Channel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sorcix/sirc/Channel.java b/src/main/java/com/sorcix/sirc/Channel.java index 6988a95..e60f2cc 100644 --- a/src/main/java/com/sorcix/sirc/Channel.java +++ b/src/main/java/com/sorcix/sirc/Channel.java @@ -383,8 +383,8 @@ protected void renameUser(final String old, final String neww) { if (this.users != null) { final User user = this.users.remove(old); if (user != null) { - user.setNick(neww); - this.users.put(user.getNickLower(), user); + user.setNick(neww); + this.users.put(user.getNickLower(), user); } } } From 8e330cb51909638e4a262398d4f5a7198ec99d66 Mon Sep 17 00:00:00 2001 From: Vic Demuzere Date: Wed, 29 Oct 2014 09:17:11 +0100 Subject: [PATCH 15/22] Fix: putIfAbsent is not available when using the Map interface --- src/main/java/com/sorcix/sirc/Channel.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/sorcix/sirc/Channel.java b/src/main/java/com/sorcix/sirc/Channel.java index e60f2cc..81f585f 100644 --- a/src/main/java/com/sorcix/sirc/Channel.java +++ b/src/main/java/com/sorcix/sirc/Channel.java @@ -29,7 +29,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.Iterator; -import java.util.Map; /** * Represents a channel on the IRC server. @@ -45,7 +44,7 @@ public final class Channel { /** The topic of this channel. */ private String topic; /** The user list. */ - private Map users; + private ConcurrentHashMap users; /** Possible channel prefixes. */ public static final String CHANNEL_PREFIX = "#&+!"; @@ -383,8 +382,8 @@ protected void renameUser(final String old, final String neww) { if (this.users != null) { final User user = this.users.remove(old); if (user != null) { - user.setNick(neww); - this.users.put(user.getNickLower(), user); + user.setNick(neww); + this.users.put(user.getNickLower(), user); } } } From a3eafeb8189e2ba139db0717f33e0e7e4ca68d8a Mon Sep 17 00:00:00 2001 From: Vic Demuzere Date: Thu, 6 Nov 2014 15:27:49 +0100 Subject: [PATCH 16/22] Specify the github project as project homepage. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3df63bb..45ce84f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 1.1.6-SNAPSHOT sIRC Sorcix Lib-IRC (sIRC) is a simple IRC library for Java. - http://vic.demuzere.be/projects/sirc/ + http://github.com/sorcix/sIRC/ jar From a6ffa19db06f1ab18c96356d3b384e7e40560cd5 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Tue, 20 Oct 2015 09:51:08 -0700 Subject: [PATCH 17/22] 1.1.6-pfn --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 323a9ce..f92b6c4 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ crossPaths := false organization := "com.hanhuy" -version := "1.1.6-SNAPSHOT" +version := "1.1.6-pfn" // sonatype publishing options follow publishMavenStyle := true From ae74a1a4ddcd55516fe97806a12bd5d95a07c921 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Tue, 20 Oct 2015 09:57:14 -0700 Subject: [PATCH 18/22] 1.1.6-pfn --- src/main/java/com/sorcix/sirc/Channel.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sorcix/sirc/Channel.java b/src/main/java/com/sorcix/sirc/Channel.java index 81f585f..e924cc4 100644 --- a/src/main/java/com/sorcix/sirc/Channel.java +++ b/src/main/java/com/sorcix/sirc/Channel.java @@ -71,7 +71,7 @@ public Channel(final String name, final IrcConnection irc, final boolean global) * * @param user The user to add. */ - protected void addUser(final User user) { + public void addUser(final User user) { if (this.users != null) { this.users.putIfAbsent(user.getNickLower(), user); } @@ -357,7 +357,7 @@ public void removeOperator(final User user) { * * @param user The user to remove. */ - protected void removeUser(final User user) { + public void removeUser(final User user) { if (this.users != null) { this.users.remove(user.getNickLower()); } From 914e5436a5846c5c3995fd6992a60d3e41b13941 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Tue, 20 Oct 2015 10:48:59 -0700 Subject: [PATCH 19/22] Add onMotd, onConnect and onDisconnect to ServerEventListener --- build.sbt | 10 ++++++++++ src/main/java/com/sorcix/sirc/IrcConnection.java | 3 +++ src/main/java/com/sorcix/sirc/IrcInput.java | 4 ++++ src/main/java/com/sorcix/sirc/IrcParser.java | 3 +++ .../com/sorcix/sirc/event/ServerEventListener.java | 13 +++++++++++++ 5 files changed, 33 insertions(+) diff --git a/build.sbt b/build.sbt index f92b6c4..1e3a817 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,15 @@ name := "sirc" +autoScalaLibrary := false + +javacOptions in Compile ++= Seq("-target", "1.6", "-source", "1.6") + +javacOptions in (Compile,doc) ~= { + _.foldRight(List.empty[String]) { case (o,r) => + if (o != "-target") o :: r else r.drop(1) + } +} + crossPaths := false organization := "com.hanhuy" diff --git a/src/main/java/com/sorcix/sirc/IrcConnection.java b/src/main/java/com/sorcix/sirc/IrcConnection.java index 1e4bdb6..f5d7dac 100644 --- a/src/main/java/com/sorcix/sirc/IrcConnection.java +++ b/src/main/java/com/sorcix/sirc/IrcConnection.java @@ -475,6 +475,9 @@ public void connect(Socket sock) throws UnknownHostException, IOException, NickN .hasNext();) { it.next().onConnect(this); } + for (ServerEventListener l : serverEventListeners) { + l.onConnect(this); + } } /** diff --git a/src/main/java/com/sorcix/sirc/IrcInput.java b/src/main/java/com/sorcix/sirc/IrcInput.java index 5a5ca8c..9644cfc 100644 --- a/src/main/java/com/sorcix/sirc/IrcInput.java +++ b/src/main/java/com/sorcix/sirc/IrcInput.java @@ -27,6 +27,8 @@ */ package com.sorcix.sirc; +import com.sorcix.sirc.event.ServerEventListener; + import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; @@ -132,5 +134,7 @@ public void run() { for (final Iterator it = this.irc.getServerListeners(); it.hasNext();) { it.next().onDisconnect(this.irc); } + for (ServerEventListener l : irc.getServerEventListeners()) + l.onDisconnect(irc); } } diff --git a/src/main/java/com/sorcix/sirc/IrcParser.java b/src/main/java/com/sorcix/sirc/IrcParser.java index c6ecf9a..5836bcd 100644 --- a/src/main/java/com/sorcix/sirc/IrcParser.java +++ b/src/main/java/com/sorcix/sirc/IrcParser.java @@ -407,6 +407,9 @@ protected void parseNumeric(final IrcConnection irc, final IrcPacket line) { for (final Iterator it = irc.getServerListeners(); it.hasNext();) { it.next().onMotd(irc, motd); } + for (ServerEventListener l : irc.getServerEventListeners()) { + l.onMotd(new ServerEventListener.Motd(irc, line, motd)); + } } break; case IrcPacket.RPL_BOUNCE: diff --git a/src/main/java/com/sorcix/sirc/event/ServerEventListener.java b/src/main/java/com/sorcix/sirc/event/ServerEventListener.java index 586da79..216872b 100644 --- a/src/main/java/com/sorcix/sirc/event/ServerEventListener.java +++ b/src/main/java/com/sorcix/sirc/event/ServerEventListener.java @@ -100,6 +100,13 @@ public Kick(IrcConnection c, IrcPacket p) { } } + public static class Motd extends BaseEvent { + public final String motd; + public Motd(IrcConnection c, IrcPacket p, String motd) { + super(c, p); + this.motd = motd; + } + } public static class Mode extends BaseEvent { public final Channel channel; public final User sender; @@ -232,4 +239,10 @@ public Topic(IrcConnection c, IrcPacket p) { * joining. */ void onTopic(Topic topic); + + void onMotd(Motd motd); + + void onConnect(IrcConnection c); + + void onDisconnect(IrcConnection c); } From db8e7efccb8820879f03a357204fcea4a82e1058 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Tue, 20 Oct 2015 10:49:16 -0700 Subject: [PATCH 20/22] 1.1.6-pfn.1 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 1e3a817..8fbebc8 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ crossPaths := false organization := "com.hanhuy" -version := "1.1.6-pfn" +version := "1.1.6-pfn.1" // sonatype publishing options follow publishMavenStyle := true From fb9691e1bb748bc084f888f2dcf069a74f811fb2 Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Fri, 29 Jan 2016 10:17:35 -0800 Subject: [PATCH 21/22] Update IrcDebug calls with connection info --- build.sbt | 2 +- src/main/java/com/sorcix/sirc/IrcInput.java | 6 +++--- src/main/java/com/sorcix/sirc/IrcOutput.java | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 8fbebc8..9fda42d 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ crossPaths := false organization := "com.hanhuy" -version := "1.1.6-pfn.1" +version := "1.1.6-pfn.2" // sonatype publishing options follow publishMavenStyle := true diff --git a/src/main/java/com/sorcix/sirc/IrcInput.java b/src/main/java/com/sorcix/sirc/IrcInput.java index 9644cfc..a885003 100644 --- a/src/main/java/com/sorcix/sirc/IrcInput.java +++ b/src/main/java/com/sorcix/sirc/IrcInput.java @@ -108,14 +108,14 @@ public void run() { try { // wait for lines to come in while ((line = this.in.readLine()) != null) { - IrcDebug.log("<<< " + line); + IrcDebug.log(irc + " <<< " + line); // always respond to PING if (line.startsWith("PING ")) { this.irc.out.pong(line.substring(5)); } else if (!line.trim().isEmpty()){ this.handleLine(line); } else { - IrcDebug.log("*** Invalid line from server, ignoring"); + IrcDebug.log(irc + ": *** Invalid line from server, ignoring"); } } } catch (final SocketException ex) { @@ -123,7 +123,7 @@ public void run() { } catch (final IOException ex) { this.irc.setConnected(false); } catch (final Exception ex) { - IrcDebug.log("Exception " + ex + " on: " + line); + IrcDebug.log(irc + ": Exception " + ex + " on: " + line); ex.printStackTrace(); } // when reaching this, we are disconnected diff --git a/src/main/java/com/sorcix/sirc/IrcOutput.java b/src/main/java/com/sorcix/sirc/IrcOutput.java index 0852437..9403508 100644 --- a/src/main/java/com/sorcix/sirc/IrcOutput.java +++ b/src/main/java/com/sorcix/sirc/IrcOutput.java @@ -187,7 +187,7 @@ public synchronized void sendNowEx(String line) throws IOException { if (line.length() > (IrcOutput.MAX_LINE_LENGTH - 2)) { line = line.substring(0, IrcOutput.MAX_LINE_LENGTH - 2); } - IrcDebug.log(">>> " + line); + IrcDebug.log(irc + " >>> " + line); this.out.write(line + IrcConnection.ENDLINE); this.out.flush(); } From ed7ecb39b65494fe85a465aa17b7c5af7803b0cd Mon Sep 17 00:00:00 2001 From: Perry Nguyen Date: Tue, 17 Jul 2018 14:11:48 -0700 Subject: [PATCH 22/22] Handle malformed PRIVMSG --- .gitignore | 1 + build.sbt | 2 +- src/main/java/com/sorcix/sirc/IrcPacket.java | 8 +++++++- .../java/com/sorcix/sirc/event/MessageEventListener.java | 8 ++++---- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 7a6a59d..8962e95 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ *~ .project .classpath +/project/target/ diff --git a/build.sbt b/build.sbt index 9fda42d..c6e93ea 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ crossPaths := false organization := "com.hanhuy" -version := "1.1.6-pfn.2" +version := "1.1.6-pfn.3" // sonatype publishing options follow publishMavenStyle := true diff --git a/src/main/java/com/sorcix/sirc/IrcPacket.java b/src/main/java/com/sorcix/sirc/IrcPacket.java index dcb4e73..4b5f814 100644 --- a/src/main/java/com/sorcix/sirc/IrcPacket.java +++ b/src/main/java/com/sorcix/sirc/IrcPacket.java @@ -164,7 +164,13 @@ protected IrcPacket(String line, final IrcConnection irc) { this.message = this.message.substring(1, this.message.length() - 1); } - } + } else { + // broken PRIVMSG with 1 message word and no colon: thelounge + String[] args = getArgumentsArray(); + if (args.length > 1) { + this.message = args[args.length - 1]; + } + } // check if the command is a server reply this.cmdNumeric = this.getInteger(this.command); if (this.cmdNumeric != -1) { diff --git a/src/main/java/com/sorcix/sirc/event/MessageEventListener.java b/src/main/java/com/sorcix/sirc/event/MessageEventListener.java index f3c2249..5eee5c8 100644 --- a/src/main/java/com/sorcix/sirc/event/MessageEventListener.java +++ b/src/main/java/com/sorcix/sirc/event/MessageEventListener.java @@ -47,7 +47,7 @@ public Action(IrcConnection c, IrcPacket p) { action = p.getMessage().substring(7); if (Channel.CHANNEL_PREFIX.indexOf(p.getArguments().charAt(0)) >= 0) { // to channel - target = c.getState().getChannel(p.getArguments()); + target = c.getState().getChannel(p.getArgumentsArray()[0]); sender = target.updateUser(p.getSender(), true); } else { // to user @@ -81,7 +81,7 @@ public Message(IrcConnection c, IrcPacket p) { message = p.getMessage(); if (Channel.CHANNEL_PREFIX.indexOf( p.getArguments().charAt(0)) >= 0) { - target = c.getState().getChannel(p.getArguments()); + target = c.getState().getChannel(p.getArgumentsArray()[0]); sender = target.updateUser(p.getSender(), true); } else { target = null; @@ -99,7 +99,7 @@ public Notice(IrcConnection c, IrcPacket p) { message = p.getMessage(); if (Channel.CHANNEL_PREFIX.indexOf( p.getArguments().charAt(0)) >= 0) { - target = c.getState().getChannel(p.getArguments()); + target = c.getState().getChannel(p.getArgumentsArray()[0]); sender = target.updateUser(p.getSender(), true); } else { target = null; @@ -134,4 +134,4 @@ public Notice(IrcConnection c, IrcPacket p) { * Received a private message. */ void onPrivateMessage(Message message); -} \ No newline at end of file +}