Below is the code I used for a test command in Discord4J
gateway.on(MessageCreateEvent.class)
.map(MessageCreateEvent::getMessage)
.filter(message -> message.getContent().contains("test"))
.flatMap(Message::getChannel)
.flatMap(channel -> channel.createMessage("test!"))
.subscribe();
but now if you type "test" in discord, it keeps sending messages, instead of a single message like I would expect. What's the issue here? It does work with equals() and equalsignorecase(), but with all other String checking methods it goes into a loop.
This is because you receive a MessageCreateEvent for all messages you send with the bot as well. Since your message is createMessage("test!"), you will also receive a message where getContent() returns "test!". This will obviously result in "test!".contains("test") but not "test!".equals("test").
This can be prevented by checking if the message author is a bot.
filter(message -> !message.getAuthor().map(User::isBot).orElse(true))
Related
I want that my Discord jda bot reads the last message of an textchannel after starting.
So I call :
textChannel.getHistory().getMessageById(config.getLatestMessageId());
The MessageHistory class doesn't have an getLatestMessage method somehow.
And for some reason textChannel.getHistory() is always empty and therefore always return null.
is there an other way to read messages (written before the bot was started).
Some aditional information:
The textchannel is the correct textchannel.
it is not empty, and I also tried writing new messages while the bot is active.
But the messagehistory is always empty.
Also something I find weird:
textchannel.gethistory().isempty() is true
and textchannel.hasLastMessage is true as well.
EDIT:
Do the following:
channel.getHistory().retrievePast(1).queue(messages -> {
// messages (list) contains all received messages
// Access them in here
// Use for example messages.get(0) to get the received message
// (messages is of type List)
});
// DON'T access the received messages outside here
// If you use queue the received messages WON'T be available directly after the call
PREVIOUS:
If I do the following on my bot it works:
channel.getHistory().retrievePast(1).queue(messages -> {
if (messages.size() > 0) System.out.println(messages.get(0).getContentDisplay());
});
After the call succeeded and the lambda is executed, calling
channel.getHistory().getRetrievedHistory()
should return the received chat history
You can also execute the action directly and block the current thread until the message history was received by doing the following:
MessageHistory h = channel.getHistory();
h.retrievePast(1).complete();
List<Message> ml = h.getRetrievedHistory();
if (ml.size() > 0) System.out.println(ml.get(0).getContentDisplay());
but I don't recommand doing this, since it will block the current thread. Use the code in the first part of my answer instead, which won't block execution, and will fill the message history with data and once its ready it will call the lambda.
Notice that I'm passing in 1 as an argument for the call to 'retrievePast'. This will only receive the last message send inside the text channel.
I guess you can't receive the entire text channel, since it would be to expensive to store all the sent data in the RAM or it would just take to long.
I keep running into situations where I want to be able to listen for a response to a request via RxJava. The problem is I am not sure how to set up the Observable so that I am listening for events and send the message on subscribe in the correct order. I don't want to send the message then listen because, if the thread gets suspended or the response is super fast, I could miss it. This is the closest I could think of on my own
connection.onReceivedMessage()
.doOnSubscribe(() -> connection.send(message))
.filter(message -> message.id == id)
... // do stuff
or
Observable.defer(() -> {
connection.send(message);
return connection.onReceivedMessage();
})... // do stuff
But these still seem like I could still send the message and not be listening for the response. Has anyone else tried to do this? I feel like I really want a sort of afterCreate().
I don't want to send the message then listen because, if the thread
gets suspended or the response is super fast, I could miss it.
Use a Subject. Either a BehaviorSubject (emits always the latest observable emitted to new subscriber) or a ReplySubject (emits all the Observable emitted to new subscriber). I am not sure about the whole logic, but you could have something like:
public BehaviorSubject mMessageBehaviorSubject = BehaviorSubject.create();
private void sendMessage() {
connection.onReceivedMessage()
.doOnSubscribe(() -> connection.send(message))
.filter(message -> message.id == id)
.subscribe(mSubject::onNext, Throwable::printStackTrace);
}
public Observable<String> getMessageObservable() {
return mMessageBehaviorSubject.asObservable();
}
this way you could send the message and whenever you are ready to listen yu will get, in this case, latest message sent
doOnSubscribe is exactly what you need, it's the "afterCreate" you are looking for. In your first example, the first message will be send after subscription, at this time the Observable is already ready to handle response.
As for your question - Has anyone else tried to do this? - the response is yes. I use same technique as your first example to rxify some piece of codes.
String queueA = "rabbitmq://host:5672/queue-a.exchange?queue=queue-a.exchange..etc
from(queueA)
.routeId("idForQueueA")
.onException(Exception.class)
.maximumRedeliveries(0)
// .processRef("sendEmailAlert") * not sure this belongs here*
.to(deadLetterQueueA)
.useOriginalMessage()
.end()
.processRef("dataProcessing")
.processRef("dataExporting")
.end();
Explaining the code above:
Messages are taken from queueA. Upon various processes being successful the message is consumed. If it fails its added to the dead letter queue "deadLetterQueueA". This all works ok.
My question is
When messages arrive in the deadletter queue I want to add alerts so we know to do something about it... How could I to add an email alert when a message arrives in the dead letter queue. I dont want to lose the original message if the alert fails - nor do I want the alert to consume the message.
My thoughts are.. I would need to split the message on an exception so its sent to two different queues? One for the alert which then sends out an email alert and then consumes itself. Then one for the dead letter queue that just sites there? However I'm not sure how to do this?
You can split a message to go to multiple endpoints using a multicast (details here):
.useOriginalMessage().multicast().to(deadLetterQueueA, "smtp://username#host:port?options")
This uses the camel mail component endpoints described here. Alternatively, you can continue processing the message after the to. So something like:
.useOriginalMessage()
.to(deadLetterQueueA)
.transform().simple("Hi <name>, there has been an error on the object ${body.toString}")
.to("smtp://username#host:port?options")
If you had multiple recipients, you could use a recipients list
public class EmailListBean {
#RecipientList
public String[] emails() {
return new String[] {"smtp://joe#host:port?options",
"smtp://fred#host:port?options"};
}
}
.useOriginalMessage()
.to(deadLetterQueueA)
.transform().simple("...")
.bean(EmailListBean.class)
Be careful of using JMS queues to store messages while waiting for a human to action them. I don't know what sort of message traffic you're getting. I'm assuming if you want to send an email for every failure, it's not a lot. But I would normally be wary of this sort of thing, and chose to use logging or database persistence to store the results of errors, and only use a JMS error queue to notify other processes or consumers of the error or to schedule a re-try.
There are two ways you can do this , but based on your message volume you might not want to send email on every failed message.
You can use the solution provided by AndyN , or you can use the Advisory Topics ActiveMQ.Advisory.MessageDLQd.Queue.* , whenever a message gets in to the DLQ the enqueue count of the topic will increase by 1 . By monitoring the Queue Depth you might now be able to send a mail to based on the number of the errors that ocurred.
If you want to do it at the producer end. You can use any one of the solutions provided by AndyN
I am trying to implement a process consisting of several webservice-calls, initiated by a JMS-message read by Spring-integration. Since there are no transactions across these WS-calls, I would like to keep track of how far my process has gone, so that steps that are already carried out are skipped when retrying message processing.
Example steps:
Retrieve A (get A.id)
Create new B for A (using A.id, getting B.id)
Create new C for B (using B.id, getting C.id)
Now, if the first attempt fails in step 3, I already have a created a B, and know it's id. So if I want to retry the message, it will skip the second step, and not leave me with an incomplete B.
So, to the question: Is it possible to decorate a JMS-message read by Spring-integration with additional header properties upon message processing failures? If so, how could I do this?
The way it works at the moment:
Message is read
Some exception is thrown
Message processing halts, and ActiveMQ places the message on DLQ
How I would like it to work:
Message is read
Some exception is thrown
The exception is handled, with the result of this handling being an extra header property added to the original message
ActiveMQ places the message on DLQ
One thing that might achieve this is the following:
Read the message
Start processing, wrapped in try-catch
On exception, get the extra information from the exception, create a new message based on the original one, add extra info to header and send it directly to the DLQ
Swallow the exception so the original message dissappears
This feels kinda hackish though, hopefully there is a more elegant solution.
It's hard to generalize without more information about your flow(s) but you could consider adding a custom request handler advice to decorate and/or re-route failed messages. See Adding Behavior to Endpoints.
As the other answer says, you can't modify the message but you can build a new one from it.
EDIT:
So, to the question: Is it possible to decorate a JMS-message read by Spring-integration with additional header properties upon message processing failures? If so, how could I do this?
Ahhh... now I think I know what you are asking; no, you can't "decorate" the existing message; you can republish it with additional headers instead of throwing an exception.
You can republish in the advice, or in the error flow.
It might seem like a "hack" to you, but the JMS API provides no mechanism to do what you want.
From the spring forum:
To place new header to the MessageHeaders you should use
MessageBuilder, because not only headers, but entire Message is
immutable.
return MessageBuilder.fromMessage(message).setHeader(updateflag, message.getHeaders().get("Lgg_Rid") == "ACK" ? "CONF" : "FAIL").build();
In an asynchronous context, errors will go to an error channel - either one you configure yourself and indicate in the message headers with errorChannel, or a global error channel if none is specified. See for more details here.
I am working on a Java application that pulls messages from an Azure Service Bus queue. I am using the Java Azure API (com.microsoft.windowsazure.services). The problem that I'm experiencing is that the deletion of brokered messages after they had been processed sometimes fails.
My application pulls a message from the queue using the receiveQueueMessage() method on a ServiceBusContract object, using peek-lock receive mode. Once the message had been sucessfully processed, I remove the message from the queue by calling the deleteMessage() method (I believe this method corresponds to the Complete() method in the .NET API).
However, sometimes this method call fails. A com.sun.jersey.api.client.UniformInterfaceException exception is logged to the console by deleteMessage(), but it does not throw this exception (I'll produce the output below). The exception seems to tell that the message could not be found. When this happens, the message stays in the queue. In fact, the next call to receiveQueueMessage() retrieves this message again. The deletion then fails once or twice more, and then it succeeds. The messages retrieved thereafter delete successfully.
Here is the code where the problem occurs:
ReceiveMessageOptions receiveOptions = ReceiveMessageOptions.DEFAULT;
receiveOptions.setReceiveMode(ReceiveMode.PEEK_LOCK);
BrokeredMessage message = serviceBus.receiveQueueMessage("my_queue",receiveOptions).getValue();
// Process the message
System.out.println("Delete message with ID: "+message.getMessageId());
serviceBus.deleteMessage(message);
Here is an example of the output when the problem occurs:
Delete message with ID: 100790000086491
2013/01/22 12:58:29 com.microsoft.windowsazure.services.serviceBus.implementation.ServiceBusExceptionProcessor processCatch
WARNING: com.sun.jersey.api.client.UniformInterfaceException: DELETE https://voyagernetzmessaging.servicebus.windows.net/sms_queue/messages/24/efa56a1c-95e8-4cd6-931a-972eac21563a returned a response status of 404 Not Found
com.sun.jersey.api.client.UniformInterfaceException: DELETE https://voyagernetzmessaging.servicebus.windows.net/sms_queue/messages/24/efa56a1c-95e8-4cd6-931a-972eac21563a returned a response status of 404 Not Found
at com.sun.jersey.api.client.WebResource.voidHandle(WebResource.java:697)
at com.sun.jersey.api.client.WebResource.delete(WebResource.java:261)
at com.microsoft.windowsazure.services.serviceBus.implementation.ServiceBusRestProxy.deleteMessage(ServiceBusRestProxy.java:260)
at com.microsoft.windowsazure.services.serviceBus.implementation.ServiceBusExceptionProcessor.deleteMessage(ServiceBusExceptionProcessor.java:176)
at microworks.voyagernetzmessaging.smsservice.SmsSender$Runner.finalizeSms(SmsSender.java:114)
at microworks.voyagernetzmessaging.smsservice.SmsSender$Runner.finalizeSms(SmsSender.java:119)
at microworks.voyagernetzmessaging.smsservice.SmsSender$Runner.run(SmsSender.java:340)
com.microsoft.windowsazure.services.core.ServiceException: com.sun.jersey.api.client.UniformInterfaceException: DELETE https://voyagernetzmessaging.servicebus.windows.net/sms_queue/messages/24/efa56a1c-95e8-4cd6-931a-972eac21563a returned a response status of 404 Not Found
Response Body: <Error><Code>404</Code><Detail>The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue..TrackingId:4b112c5a-5919-4680-b6bb-e10a2c081ba3_G15_B9,TimeStamp:1/22/2013 10:58:30 AM</Detail></Error>
at com.microsoft.windowsazure.services.serviceBus.implementation.ServiceBusExceptionProcessor.deleteMessage(ServiceBusExceptionProcessor.java:179)
at microworks.voyagernetzmessaging.smsservice.SmsSender$Runner.finalizeSms(SmsSender.java:114)
at microworks.voyagernetzmessaging.smsservice.SmsSender$Runner.finalizeSms(SmsSender.java:119)
at microworks.voyagernetzmessaging.smsservice.SmsSender$Runner.run(SmsSender.java:340)
Caused by: com.sun.jersey.api.client.UniformInterfaceException: DELETE https://voyagernetzmessaging.servicebus.windows.net/sms_queue/messages/24/efa56a1c-95e8-4cd6-931a-972eac21563a returned a response status of 404 Not Found
at com.sun.jersey.api.client.WebResource.voidHandle(WebResource.java:697)
at com.sun.jersey.api.client.WebResource.delete(WebResource.java:261)
at com.microsoft.windowsazure.services.serviceBus.implementation.ServiceBusRestProxy.deleteMessage(ServiceBusRestProxy.java:260)
at com.microsoft.windowsazure.services.serviceBus.implementation.ServiceBusExceptionProcessor.deleteMessage(ServiceBusExceptionProcessor.java:176)
... 3 more
Do note that the URI in the exception seems to refer to a different message ID (efa56a1c-95e8-4cd6-931a-972eac21563a, while the message's ID is in fact 100790000086491). I do not know if this could be a key to the failure, but I have a hunch.
Another interesting observation: it looks as though the error always happens with the first message that is retrieved from the queue after the application had been started, or after the queue had been empty. All the messages coming thereafter don't seem to ever cause this type of problem.
The queue has a lock duration of 2 minutes, and the processing of the messages takes well under that duration, so an expiring lock cannot be the cause.
Any ideas?
I would suggest you to call Complete() of BrokeredMessage class.
So in your case, try calling:
message.Complete();
When the Service bus sees Complete(), it considers the message to be consumed and removes it from the queue.
The UUID that appears in the URL is a random token that the server uses to track which message is locked; it is not supposed to the be same as the message id. You can access the lock URL using message.getLockLocation().
The code you have looks correct, I cannot see any obvious reason why it would fail, especially in the say you describe. Some things to check:
Check that the message you get is a valid message. If you peek-lock an empty queue, it will return an empty message. Then the lock location should be null. (But that would not cause the failure you see.)
You could get the lock supplied is invalid error if you are trying to delete the same message more than once. That could happen if you have code that notices when the service returns an empty message, and substitutes the previous message. (But that would not explain why trying to delete the message eventually works, unless it is a different message that is getting deleted.)
Hopefully that will help!