Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<smack-version>4.1.8</smack-version>
<jackson-version>2.9.1</jackson-version>
</properties>

<dependencies>
Expand Down Expand Up @@ -64,6 +65,20 @@
<artifactId>smack-java7</artifactId>
<version>4.1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-version}</version>
</dependency>


</dependencies>

<build>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/sk/mizik/quentin/Constants.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package sk.mizik.quentin;

/**
*
* Static constant library
*
* @author Marian Mizik
* @since 1.0.0
*/
Expand Down
39 changes: 22 additions & 17 deletions src/main/java/sk/mizik/quentin/Quentin.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,44 @@
import javax.inject.Inject;

/**
* Quentin's startup point.
* <p>
* Responsible of dispatching startup event to all subsequent startup routines.
*
* @author Marian Mizik
* @since 1.0.0
*/
@ApplicationScoped
public class Quentin {

@Inject
private XmppService xmppService;

@Inject
private Config config;

//HACK TO IMPLEMENT @Startup FUNCTIONALITY WITHOUT EJB, IN CDI ONLY CONTAINER
private void start(
@Observes(during = TransactionPhase.IN_PROGRESS)
@Initialized(ApplicationScoped.class)
Object init
) {
config.load();
xmppService.init();
}
@Inject
private XmppService xmppService;

@Inject
private Config config;

//HACK TO IMPLEMENT @Startup FUNCTIONALITY WITHOUT EJB, IN CDI ONLY CONTAINER
private void start(
@Observes(during = TransactionPhase.IN_PROGRESS)
@Initialized(ApplicationScoped.class)
Object init
) {
config.load();
xmppService.init();
}
}

//TODO replace path detection logic to make it application server implementation independent

//TODO aliases for requesting one command (or even full nice sentences)
//TODO casual chat feature
//TODO check regex validity

//TODO check jwt validity/details
//TODO translate words and phrases sk2en and en2sk
//TODO check for lunch menu
//TODO control redmine remotely (define ideal set of features)
//TODO control jenkins remotely (define ideal set of features)
//TODO get profile of person, colleague, customer
//TODO Searching WIKI
//TODO implement Q&A mechanism with search

//TODO crontab syntax helper (https://crontab.guru/#0/2_*_*_*_*)
223 changes: 117 additions & 106 deletions src/main/java/sk/mizik/quentin/XmppService.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,116 +28,127 @@
import java.util.concurrent.ConcurrentHashMap;

/**
* Service that handles all incoming and outgoing XMPP connection.
* <p>
* Service is aware of all {@link Command} implementations and dispatches requests sent to the Quentin.
*
* @author Marian Mizik
* @since 1.0.0
*/
@ApplicationScoped
public class XmppService {

@Inject
@Any
Instance<Command> commands;

@Inject
private Config config;

private final Logger LOG = LoggerFactory.getLogger(XmppService.class);

private final Map<String, Chat> activeChats = new ConcurrentHashMap<>();

void init() {
AbstractXMPPConnection connection;
try {
//CONNECT TO XMPP SERVER
XMPPTCPConnectionConfiguration xcc = XMPPTCPConnectionConfiguration.builder()
.setHost(config.getString(Constants.XMPP_HOST))
.setPort(config.getNumber(Constants.XMPP_PORT))
.setUsernameAndPassword(config.getString(Constants.XMPP_USER), config.getString(Constants.XMPP_PASS))
.setServiceName(config.getString(Constants.XMPP_HOST))
.setHostnameVerifier((s, sslSession) -> true)
.build();

connection = new XMPPTCPConnection(xcc);
connection.connect().login();

//SET PRESENCE STATUS
Presence presence = new Presence(Presence.Type.available);
presence.setStatus("Quentin has arrived!");
connection.sendStanza(presence);

//LISTEN FOR ANY INCOMING MESSAGES
ChatManager chatManager = ChatManager.getInstanceFor(connection);
chatManager.addChatListener(
(newChat, createdLocally) -> {
if (!createdLocally) {
activeChats.put(newChat.getParticipant(), newChat);
newChat.addMessageListener(new QuentinChatListener());
}
});
LOG.info("Quentin is connected and listening");
} catch (SmackException | IOException | XMPPException | NullPointerException e) {
System.err.println(e.getMessage());
}
}

//OBSERVE ASYNC RESULT EVENT AND SEND IT BACK TO SENDER
public void resultEventObserver(@Observes Result result) {
if (result.getStatus() != Status.OK) {
LOG.error(result.getValue());
}
try {
activeChats.get(result.getSender()).sendMessage(result.getValue());
} catch (SmackException.NotConnectedException e) {
LOG.error("Failed to execute command. Quentin is not connected");
}
}

//LISTEN FOR INCOMING CHAT MESSAGES, CONSTRUCT PROPER RESULT OBJECT AND SEND IT BACK TO SENDER
private class QuentinChatListener implements ChatMessageListener {
@Override
public void processMessage(Chat chat, Message message) {
if (message.getBody() != null) {

delay();

String sanitizedBody = message.getBody().toLowerCase().trim();
try {
//CHECK IF IT IS NORMALIZED COMMAND
if (sanitizedBody.startsWith("q:")) {
String[] parts = sanitizedBody.substring(2).split(" ");
boolean responded = false;
//LOOP OVER ALL FOUND COMMANDS AND IF SOME MATCHES, THEN EXECUTE
for (Command c : commands) {
if (c.getName().equalsIgnoreCase(parts[0])) {

Result result = c.execute(chat.getParticipant(), Arrays.copyOfRange(parts, 1, parts.length));
if (result.getStatus() != Status.OK) {
LOG.error(result.getValue());
}
chat.sendMessage(result.getValue());
responded = true;
break;
}
}
if (!responded) {
chat.sendMessage("I do not recognize such command. Sorry.");
}
} else {
chat.sendMessage("No idea what are you talking about.");
}
} catch (SmackException.NotConnectedException e) {
LOG.error("Failed to execute command. Quentin is not connected");
}
}
}
}

//GENERATE DELAY TO MAKE IT LOOK MORE NATURAL IN CHAT
private void delay() {
try {
Thread.sleep((long) 1000);
} catch (InterruptedException e) {
//DO NOTHING
}
}
@Inject
@Any
Instance<Command> commands;

@Inject
private Config config;

private final Logger LOG = LoggerFactory.getLogger(XmppService.class);

private final Map<String, Chat> activeChats = new ConcurrentHashMap<>();

/**
* Initialize connection to the XMPP server
*/
void init() {
AbstractXMPPConnection connection;
try {
//CONNECT TO XMPP SERVER
XMPPTCPConnectionConfiguration xcc = XMPPTCPConnectionConfiguration.builder()
.setHost(config.getString(Constants.XMPP_HOST))
.setPort(config.getNumber(Constants.XMPP_PORT))
.setUsernameAndPassword(config.getString(Constants.XMPP_USER), config.getString(Constants.XMPP_PASS))
.setServiceName(config.getString(Constants.XMPP_HOST))
.setHostnameVerifier((s, sslSession) -> true)
.build();

connection = new XMPPTCPConnection(xcc);
connection.connect().login();

//SET PRESENCE STATUS
Presence presence = new Presence(Presence.Type.available);
presence.setStatus("Quentin has arrived!");
connection.sendStanza(presence);

//LISTEN FOR ANY INCOMING MESSAGES
ChatManager chatManager = ChatManager.getInstanceFor(connection);
chatManager.addChatListener(
(newChat, createdLocally) -> {
if (!createdLocally) {
activeChats.put(newChat.getParticipant(), newChat);
newChat.addMessageListener(new QuentinChatListener());
}
});
LOG.info("Quentin is connected and listening");
} catch (SmackException | IOException | XMPPException | NullPointerException e) {
System.err.println(e.getMessage());
}
}

/**
* OBSERVE ASYNC RESULT EVENT AND SEND IT BACK TO SENDER
*
* @param result Result sent from any {@link Command} via CDI Observer events
*/
public void resultEventObserver(@Observes Result result) {
if (result.getStatus() != Status.OK) {
LOG.error(result.getValue());
}
try {
activeChats.get(result.getSender()).sendMessage(result.getValue());
} catch (SmackException.NotConnectedException e) {
LOG.error("Failed to execute command. Quentin is not connected");
}
}

//LISTEN FOR INCOMING CHAT MESSAGES, CONSTRUCT PROPER RESULT OBJECT AND SEND IT BACK TO SENDER
private class QuentinChatListener implements ChatMessageListener {
@Override
public void processMessage(Chat chat, Message message) {
if (message.getBody() != null) {

delay();

String sanitizedBody = message.getBody().toLowerCase().trim();
try {
//CHECK IF IT IS NORMALIZED COMMAND
if (sanitizedBody.startsWith("q:")) {
String[] parts = sanitizedBody.substring(2).split(" ");
boolean responded = false;
//LOOP OVER ALL FOUND COMMANDS AND IF SOME MATCHES, THEN EXECUTE
for (Command c : commands) {
if (c.getName().equalsIgnoreCase(parts[0])) {

Result result = c.execute(chat.getParticipant(), Arrays.copyOfRange(parts, 1, parts.length));
if (result.getStatus() != Status.OK) {
LOG.error(result.getValue());
}
chat.sendMessage(result.getValue());
responded = true;
break;
}
}
if (!responded) {
chat.sendMessage("I do not recognize such command. Sorry.");
}
} else {
chat.sendMessage("No idea what are you talking about.");
}
} catch (SmackException.NotConnectedException e) {
LOG.error("Failed to execute command. Quentin is not connected");
}
}
}
}

//GENERATE DELAY TO MAKE IT LOOK MORE NATURAL IN CHAT
private void delay() {
try {
Thread.sleep((long) 1000);
} catch (InterruptedException e) {
//DO NOTHING
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import javax.enterprise.context.ApplicationScoped;

/**
* Decode base64 encoded string
*
* @author Marian Mizik
* @see sk.mizik.quentin.commands.Command
* @since 1.0.0
Expand Down
38 changes: 20 additions & 18 deletions src/main/java/sk/mizik/quentin/commands/Base64EncodeCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,34 @@
import java.nio.charset.StandardCharsets;

/**
* Encode string to base64 format
*
* @author Marian Mizik
* @see sk.mizik.quentin.commands.Command
* @since 1.0.0
*/
@ApplicationScoped
public class Base64EncodeCommand implements Command {

@Override
public String getName() {
return "base64encode";
}
@Override
public String getName() {
return "base64encode";
}

@Override
public String getDescription() {
return "base64encode encode provided string using base64";
}
@Override
public String getDescription() {
return "base64encode encode provided string using base64";
}

@Override
public String getManual() {
return "\nEncode plain text string using Google Guava implementation.\n" +
"Example: q:base64encode login:password";
}
@Override
public String getManual() {
return "\nEncode plain text string using Google Guava implementation.\n" +
"Example: q:base64encode login:password";
}

@Override
public Result execute(String sender, String[] params) {
String value = BaseEncoding.base64().encode(String.join(" ", params).getBytes(StandardCharsets.UTF_8));
return new Result(Status.OK, sender, value);
}
@Override
public Result execute(String sender, String[] params) {
String value = BaseEncoding.base64().encode(String.join(" ", params).getBytes(StandardCharsets.UTF_8));
return new Result(Status.OK, sender, value);
}
}
Loading