Does hubot have a similar feature/workaround to botkit's conversation feature? (& making hubot forget responses) - coffeescript

Mostly trying to check if what I am trying to do is possible as I have struggled to find any similar examples online.
I'm trying to create a series of menu's using hubot's framework so that individual commands and values do not have to be memorised. Instead you simply enter one command at the beginning, give relevant information once where these values will be stored for use multiple times later down the menus.
Something like this:
robot.hear /blah/i, id:'start', (msg) ->
currentPosition = 'start'
msg.send "Please select an Option"
selectOption msg
selectOption = (msg) ->
currentPosition = 'selectOption'
msg.send "Option 1"
msg.send "Option 2"
robot.hear /(.*)/i, id:'selectOption', (msg) ->
displayOption1 msg if msg.match is '1'
displayOption2 msg if msg.match is '2'
displayOption1 = (msg) ->
currentPosition = 'displayOption1'
msg.send "Please enter some information"
robot.hear /(.*)/i, id: 'displayOption1', (msg) ->
# if information is entered do something with information
# pass the information onto another method etc...
# ....
# methods to do something with information and feedback results
# ....
# ....
# methods corresponding to option 2
# ....
# ....
# methods to do something with information and feedback results
# ....
end = (msg) ->
currentPosition = 'none'
msg.send "Thank you for using our service"
I have been using listener middleware to make sure that you cannot access the menu's out of order:
robot.listenerMiddleware (context, next, done) ->
listenerID = context.listener.options?.id
return unless listenerID?
try
if listenerID is 'start'
if currentPosition is 'none'
next()
else
done()
if listenerID is 'selectOption'
if currentPosition is 'selectOption'
next()
# etc...
# Other similar examples to above for every "menu" ...
catch err
robot.emit ('error', err, context.response)
Everything seems to work as expected the first time I go through the menu's however the problems start if I try and launch from the start for a second time. Values seem to get remembered even if I set them to null at the start or end of my methods. And when I get closer to the end it will start printing out things twice.
I assume this is because values are getting cached/stored elsewhere and I need to reset this. I also assume that the reason it's printing out twice is because hubot remembers that I have already been through the menu's once and there's two instances running at once (it will start printing out three times if I go through a third time). However it only seems to do this towards the end and will print out as expected for the first few methods.
Simply, is there a way to make hubot forget everything perhaps in the 'end' method so it runs like I'm running it for the first time every time? I've looked into this but things like robot.shutdown don't seem to work.
If the above isn't possible, is there are workaround?
Edit: If it at all helps, I'm trying to make something similar to botkit's conversation feature: https://github.com/howdyai/botkit#multi-message-replies-to-incoming-messages

I got linked to this on my thread on github asking the same question:
https://github.com/lmarkus/hubot-conversation
Currently trying it out to see if it solves the issue I'm having, if not hopefully it will help out other people having a similar problem to me.

Related

SNMPTraps: pysnmp and "disableAuthorization"

I have written an SNMP trap receiver but currently I have to hardcode all the SNMPv2 community strings to be able to receive the traps.
How do I emulate the 'disableAuthorization' functionality from snmptrapd in pysnmp?
I have tried to not set the community string: config.addV1System(snmpEngine, 'my-area') but this errors about a missing param. I have also tried an empty string: config.addV1System(snmpEngine, 'my-area', '') but this stops all traps being processed.
What is the best way to allow receiving all traps through pysnmp regardless of the community string they were sent with? I haven't found anything in the pysnmp docs that could help me
I had made progress on setting up an observer for V1/2 (V3 to be added later) that picked up on notifications with an unknown community string and then called addV1System on the fly to dynamically add it in, like so:
When setting up the transportDispatcher:
snmpEngine.observer.registerObserver(_handle_unauthenticated_snmptrap,
"rfc2576.prepareDataElements:sm-failure", "rfc3412.prepareDataElements:sm-failure")
And then:
def _handle_unauthenticated_snmptrap(snmpEngine, execpoint, variables, cbCtx):
if variables["securityLevel"] in [ 1, 2 ] and variables["statusInformation"]["errorIndication"] == errind.unknownCommunityName:
new_comm_string = "%s" % variables["statusInformation"].get("communityName", "")
config.addV1System(my_snmpEngine, 'my-area', new_comm_string)
return
else:
msg = "%s" % variables["statusInformation"]
print(f"Trap: { msg }")
However, this will always throw away the first trap received while adding any new community string (and then there is the problem whereby when the daemon is restarted the updated list of community strings is lost).
In looking to improve this I then found hidden away in the docs this little gem:
https://pysnmp.readthedocs.io/en/latest/examples/v3arch/asyncore/manager/ntfrcv/advanced-topics.html#serve-snmp-community-names-defined-by-regexp
This example receives a notification and rewrites the community string into 'public' so all traps will be correctly received.
In essence, when setting up the transportDispatcher:
my_snmpEngine.observer.registerObserver(_trap_observer,
'rfc2576.processIncomingMsg:writable', cbCtx='public')
And then:
def _trap_observer(snmpEngine, execpoint, variables, community_string):
variables['communityName'] = variables['communityName'].clone(community_string)

Gatling: Access variables from saved "findAll" list in foreach loop

I'm new to Gatling and Scala, and I had a hopefully quick and basic question about how to access the elements that are saved as from a findAll in the previous request.
The regex in the below code matches multiple button values. I eventually want to find the "max" button value (by something I'll come up with later), and based on that use that button in subsequent requests. However, I'm unable to actually access the values in button_list. In the terminal when I try to print the values, the values don't get substituted and literally print like this for each button:
Button ${count}: ${button}
Button ${count}: ${button}
Here's the snippet producing this:
...
.exec(http("click_ok")
.post("www.foo.com")
.headers(headers_0)
.formParam("_flowExecutionKey", "${flow_execution_key}")
.formParam("_eventId_submit", "${_eventId_submit}")
.check(regex("""foo(.*?)bar""").findAll.saveAs("button_list"))).exitHereIfFailed
.pause(1)
.foreach("${button_list}", "button", "count") {
exec(session => {
println("Button ${count}: ${button}")
session})
}
...
When I see the session print out in the logs, I can see that the buttons have matched and the session contains a list like the following, so I know there are successful matches:
button_list -> List(c11/98/280, c11/98/390)
Anyone have an example or know what I'm doing wrong?
Thanks!
As explained in the official documentation, Gatling Expression Language is not something that magically works anywhere. It only works when passing such String to a Gatling DSL method, not in your own code. You must use the Gatling Session API.

perl6 Changes in IO::Socket::INET from last year and broken promises

When I asked a question last year about promises, my echo server was working (see this link: perl6 how to get specific identity of promises? ). However, with the new versions of perl6, my echo server is no longer working.
I guess I can try the example from the perl6 documentation site ( https://docs.perl6.org/type/IO::Socket::INET ), but I want to find out what mistake I have made in my code. My current level has precluded me from seeing the difference between my codes and the codes on the perl6 documentation site. Please give me a hint; thanks !
my #result;
for 0 .. 2 -> $index {
#result[$index] = start {
my $myPromiseID = $index;
say "======> $myPromiseID\n";
my $rsSocket = IO::Socket::INET.new:
localhost => 'localhost',
localport => 1234 + $index,
listen => 1;
while $rsSocket.accept -> $rsConnection {
say "Promise $myPromiseID accepted connection";
while $rsConnection.recv -> $stuff {
say "Promise $myPromiseID Echoing $stuff";
$rsConnection.print($stuff);
}
$rsConnection.close;
}
}
}
await #result;
And the error messages are:
Tried to get the result of a broken Promise
in block <unit> at p6EchoMulti.pl line 24
Original exception:
Nothing given for new socket to connect or bind to
in block at p6EchoMulti.pl line 8
Actually thrown at:
in block at p6EchoMulti.pl line 13
This commit, which was announced in the Jan 2017 section of Rakudo's changelog as "Fixed bug where IPv6 URIs were not parsed correctly" did a lot more that just fix a URI parsing bug. It also completely redid the parameter binding/validation of an IO::Socket::INET.new call, and one consequence is it broke your code because the updated code requires that listen be an actual Bool, not merely coerce to one.
The old code (the code on the left of the commit link above) had a simple method new (*%args is copy). This matched your call. The error (fail "Nothing given for new socket to connect or bind to") did not trigger because 1 evaluates to True in a boolean context so %args<host> || %args<listen> was also True. So the rest of the code ran with listen set to 1 and it all worked out fine.
Rakudos from 2017.01 have the code on the right at the commit link above. Note how there are now multiple new methods (i.e. multiple multi method new ... declarations).
The multi(s) intended to handle a call that specifies a listen argument is/are of the form multi method new (..., Bool:D :$listen!, ...). Note the Bool:D.
A call to new, with the listen parameter set to True, matches this multi and works as expected.
But a call with :listen(1) will just match the generic multi method new (*%args) signature instead. This latter does an unconditional fail "Nothing given for new socket to connect or bind to";.
Okay, after some struggling, it seems to have improved if I changed listen=>1 to listen=>True.
Can anyone care to explain why 1 was not evaluated to True, and why it worked before?
Thanks.

Combine spawned process outputs

While the following code works great to call a python script and get the output:
s = spawn 'python', ['-u', 'foo.py']
s.stdout.on 'data', (data) -> msg.send data.toString()
s.stderr.on 'data', (data) -> msg.send data.toString()
foo.py returns many different responses (it returns updates as it runs).
For instance:
def function1():
print "Function 1 complete"
def function2():
print "Function 2 complete"
function1()
function2()
Hubot does not display those results in a consistent order. I know this can happen if msg.sendis called multiple times.
While I know I could re-write foo.py to behave differently, other processes depend on foo.py and I can't alter its behavior.
I was wondering what the process might be to collect the responses as they come and send a single msg.send in hopes that a single call to msg.send will preserve the order of the process outputs.
Imho, best would be to use promises, by the addition of q or bluebird
https://documentup.com/kriskowal/q/
http://bluebirdjs.com/docs/getting-started.html
It would lead, for example, to:
Promise.all([function1(), function2()]).then (res) ->
msg.send res.join(", ")

CGI::Application and SQLite

I've been messing around with CGI::application the past couple of days and decided to create a really basic forum: the first page displays all posts (only first level, no replies or anything) and a form which can be used to create a new post.
The issue I'm running into is that the data that gets entered into the form never gets inserted into the SQLite database.
Here's the sub procedure I'm having trouble with:
sub newpost {
my $self = shift;
if ( $self->param() ){
my $dbh = DBI->connect("dbi:SQLite:dbname=$database_file","","");
my $sth = $dbh->prepare("INSERT INTO posts (author, time, text) VALUES('testuser', '2011-10-23', 'This is a test!')");
$sth->execute();
$self->header_type('redirect');
$self->header_props(-url=> '?rm=viewall');
}
else {
my $tmpl_obj = $self->load_tmpl('newpost.html');
return $tmpl_obj->output();
}
What happens correctly is that when the newpost run mode is first called, the code within the else statement is run (the template with the form is loaded). The action for the form calls this same run mode, but now that parameters are being provided, the code in the if statement is run. I've checked the SQL code itself and it works, so there must be something else I'm over looking.
Also, is it considered best practice to go about implementing the form logic in this way?
Thanks
You're confusing $self->param() with $self->query->param. The 1st is per-request application level parameters (stuff you might set in one method and use again in another method) and the 2nd are the parameters from the GET query string or the POST body of the request. If you're expecting something from the user it will be in $self->query->param.
BTW, the $self->query object is a normal CGI object, so see it's documentation for specifics.