Python Telegram bot - step into conversation - bots

I have 3 conversations set up in my telegram bot.
def main() -> None:
application = Application.builder().token(TELEGRAM_TOKEN).build()
conv_handler = ConversationHandler(
entry_points=[CommandHandler("start", start)],
states={
START_ROUTES: [
..............
],
STATION_CONTROL: [
.............
],
},
fallbacks=[CommandHandler("start", start)],
)
conv_handler_qr = ConversationHandler(
entry_points=[MessageHandler(filters.PHOTO & (~filters.FORWARDED), photo)],
states={
STATION_CONTROL: [
.... another state control handle
]
},
fallbacks=[CommandHandler("start", start)],
)
application.add_handler(conv_handler)
application.add_handler(conv_handler_qr)
application.add_handler(MessageHandler(filters.TEXT, check_code))
application.run_polling()
One conversation starts by command /start another one by sending PHOTO and third one if I send any text to the bot.
The idea: if text contains specific information, I would like to switch to another conversation (lets say number 1 with specific state) because it will completely repeat that path after.
Is it possible to realize such behavior?

Related

Chrome Extension | Multiple alarms going off at once

I am creating a task reminder extension. The user has an option to keep adding tasks and set reminders for each task.
I am using chrome.storage to store these tasks and using onChanged listener on storage to create an alarm for each task added to the storage.
But the issue is that if I set a reminder of 2 mins for a task and 3 mins for another task. Then at the end of 2 mins I am getting notification for both the tasks and at the end of 3mins I again get notifications for both the tasks.
background.js
chrome.storage.onChanged.addListener(function(changes, namespace) {
let id = (changes.tasks.newValue.length)-1
let data = changes.tasks.newValue[id]
if(data.task && data.hrs && data.min){
let totalMins = (parseInt(data.hrs*60))+parseInt(data.min)
let alarmTime = 60*1000*totalMins
chrome.alarms.create("remind"+id,{when:Date.now() + alarmTime})
}
chrome.alarms.onAlarm.addListener(()=>{
let notifObj = {
type: "basic",
iconUrl:"./images/logo5.png",
title: "Time to complete you task",
message: data.task
}
chrome.notifications.create('remindNotif'+id, notifObj)
})
popup.js
let hrs = document.querySelector("#time-hrs")
let min = document.querySelector("#time-min")
let submitBtn = document.querySelector("#submitBtn")
let task = document.querySelector("#task")
hrs.value = 0;
min.value = 1
hrs.addEventListener('change',()=>{
if (hrs.value < 0){
hrs.value =0;
}
})
min.addEventListener('change',()=>{
if (min.value < 1){
min.value = 1;
}
})
submitBtn.addEventListener("click", ()=>{
if(task.value){
chrome.storage.sync.get('tasks',(item)=>{
let taskArr = item.tasks ? item.tasks : []
linkArr.push({task:task.value, hrs:hrs.value, min:min.value})
chrome.storage.sync.set({ 'tasks' : taskArr })
})
};
});
manifest.json
{
"name" : "Link Snooze",
"description" : "This extension reminds you to open your saved links",
"manifest_version":2,
"version":"0.1.0",
"icons":{
"16":"./images/logo5.png",
"48":"./images/logo5.png",
"128":"./images/logo5.png"
},
"browser_action":{
"default_popup":"popup.html",
"default_icon":"./images/logo5.png"
},
"permissions":["storage", "notifications","alarms"],
"background" : {
"scripts": ["background.js"],
"persistent" : false
},
"options_page":"options.html"
}
Problem.
You register a new onAlarms listener when the storage changes in addition to the old listeners. All of them run each time one alarm is triggered.
Solution.
When using a non-persistent background script, all API listeners must be registered just once for the same function and it must be done synchronously, not inside an asynchronous callback or await or then(), otherwise the event will be lost when the background script auto-terminates and then wakes up for this event. The convention is to do it at the beginning of the script. The reason it worked for you until now is that the background script is kept alive while the popup is open or while devtools for the background script was open.
Such listeners evidently won't be able to use the variables from an asynchronous callback directly like data.task in your code. The solution is to use a different method of attaching data to an event, for example, create the alarm with a name that already contains the data, specifically data.task.
chrome.alarms.create(data.task, {delayInMinutes: hrs * 60 + min});
onAlarm event provides the alarm as a parameter so you can use its name, see the documentation.
Random hints:
An object can be used as an alarm name if you call JSON.stringify(obj) when creating and JSON.parse(alarm.name) in onAlarm.
In the popup, instead of manually adjusting out-of-range values, use a number input in html:
<input id="time-min" type=number min=0 max=59 step=1>
Then read it as a number: document.querySelector("#time-min").valueAsNumber || 0

How to send different message on same group for different users in django channels

I am trying to build a real-time ticket reservation app so I want to display the real time seat booking status to users.
For example; I have user1 and user2 connected to a group reservation-1
Now when the user1 selects the ticket in group reservation-1 the seat status will be converted to selected and the message should be broadcasted to all the users connected to the same group with the status of unavailable for that particular seat. But for user1 it should be the status of selected.
My code implementation for JavaScript:
const socket = new WebSocket('ws://${window.location.host}/ws/reservation-1');
const seats = document.querySelectorAll('.seats');
seats.forEach(seat => {
seat.addEventListener('click', () => {
if(seat.dataset.isSelected) {
// This is an event when user select the seat
socket.send(JSON.stringify({
"event": "SELECT",
"seat": "seat_id"
}));
}else {
// This is an event when user deselect the seat
socket.send(JSON.stringify({
"event": "DESELECT",
"seat": "seat_id"
}));
}
}
});
socket.onmessage = function(e) {
const data = JSON.parse(e.data);
// code logic to update the seat based on return values of seat status.
}
My Django server-side consumer code using django-channels:
import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer
class ReservationConsumer(WebsocketConsumer):
def connect(self):
self.chat_group_name = 'reservation-1'
async_to_sync(self.channel_layer.group_add)(
self.reservation_group_name,
self.channel_name
)
self.accept()
def disconnect(self, close_code):
async_to_sync(self.channel_layer.group_discard)(
self.reservation_group_name,
self.channel_name
)
def receive(self, text_data):
data = json.loads(text_data)
user = self.scope['user']
event = data['event']
seat_id = data['seat_id']
if event == 'SELECT':
# this code saves the seat status as selected in database
save_seat_status_to_selected(user, seat_id)
if event == 'DESELECT':
# this code saves the seat status as selected in database
remove_the_seat_status_of_selected(user, seat_id)
# this code is now sending a group message to all the users connected to the group.
# I now want to differentiate the message based on the user who selected the seat
# to pass only selected status to the event emitter and the rest of the user with the reserved
async_to_sync(self.channel_layer.group_send)(
self.reservation_group_name,
{
# get_reserved_seat will get all the seat status that are in reserved state and also the seat selected by other users
'reserved': get_reserved_seat(),
# get_selected_seat will get the seat selected by the user
'selected': get_selected_seat(user)
}
)
I am using channel_redis for group messaging. And my setting config for the channel layer is:
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
Update
I am using django-channels-presence to figure out who the users are connected in the same group(room). Is there a way to send a specific message to each user in the same connected group?
Suggestion on better code implementation that can be scalable is highly appreciated.

How to find ActiveDialog (waterfall-step) in context after replacedialog in waterfall dialog

Contextual help in prompts
I need to implement contextual help for a chatbot. My strategy is to use the active prompt as an index for a table with help-textlines. I am struggling with finding the active prompt after a stepContext.replaceDialog() in a waterfall dialog.
I will use the Compex Dialog sample as example.
In reviewSelectionDialog below is a prompt called CHOICE_PROMPT. This is the prompt in which I would like to add contextual help. If the user enters help, the helptext should be shown that is about that prompt.
In the same dialog is a loopstep. Based on a user decision, the dialog is repeated (looped) by the replaceDialog() method.
ReviewSelectionDialog is extended with CancelAndHelpDialog. As a result I am able to check for and act on any user interrupts like 'help'.
In CancelAndHelpDialog I need the active prompt when help was entered by the user so I am able to show relevant help. (CHOICE_PROMPT in this example).
My question
In the first pass of ReviewSelectionDialog, after sending 'help', I am able to get the active prompt in the CancelAndHelpDialog via innerDc.activeDialog.id. But after the stepContext.replaceDialog() in loopStep and sending 'help' again in the CHOICE_PROMPT, innerDc.activeDialog.id shows REVIEW_SELECTION_DIALOG. Where do I find the active prompt after a replace_dialog()?
ReviewSelectionDialog
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { ChoicePrompt, WaterfallDialog } = require('botbuilder-dialogs');
const REVIEW_SELECTION_DIALOG = 'REVIEW_SELECTION_DIALOG';
const { CancelAndHelpDialog } = require('./cancelAndHelpDialog');
const CHOICE_PROMPT = 'CHOICE_PROMPT';
const WATERFALL_DIALOG = 'WATERFALL_DIALOG';
class ReviewSelectionDialog extends CancelAndHelpDialog {
constructor() {
super(REVIEW_SELECTION_DIALOG);
// Define a "done" response for the company selection prompt.
this.doneOption = 'done';
// Define value names for values tracked inside the dialogs.
this.companiesSelected = 'value-companiesSelected';
// Define the company choices for the company selection prompt.
this.companyOptions = ['Adatum Corporation', 'Contoso Suites', 'Graphic Design Institute', 'Wide World Importers'];
this.addDialog(new ChoicePrompt(CHOICE_PROMPT));
this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
this.selectionStep.bind(this),
this.loopStep.bind(this)
]));
this.initialDialogId = WATERFALL_DIALOG;
}
async selectionStep(stepContext) {
// Continue using the same selection list, if any, from the previous iteration of this dialog.
const list = Array.isArray(stepContext.options) ? stepContext.options : [];
stepContext.values[this.companiesSelected] = list;
// Create a prompt message.
let message = '';
if (list.length === 0) {
message = `Please choose a company to review, or \`${ this.doneOption }\` to finish.`;
} else {
message = `You have selected **${ list[0] }**. You can review an additional company, or choose \`${ this.doneOption }\` to finish.`;
}
// Create the list of options to choose from.
const options = list.length > 0
? this.companyOptions.filter(function(item) { return item !== list[0]; })
: this.companyOptions.slice();
options.push(this.doneOption);
// Prompt the user for a choice.
return await stepContext.prompt(CHOICE_PROMPT, {
prompt: message,
retryPrompt: 'Please choose an option from the list.',
choices: options
});
}
async loopStep(stepContext) {
// Retrieve their selection list, the choice they made, and whether they chose to finish.
const list = stepContext.values[this.companiesSelected];
const choice = stepContext.result;
const done = choice.value === this.doneOption;
if (!done) {
// If they chose a company, add it to the list.
list.push(choice.value);
}
if (done || list.length > 1) {
// If they're done, exit and return their list.
return await stepContext.endDialog(list);
} else {
// Otherwise, repeat this dialog, passing in the list from this iteration.
return await stepContext.replaceDialog(REVIEW_SELECTION_DIALOG, list);
}
}
}
module.exports.ReviewSelectionDialog = ReviewSelectionDialog;
module.exports.REVIEW_SELECTION_DIALOG = REVIEW_SELECTION_DIALOG;
CancelAndHelpDialog
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { InputHints } = require('botbuilder');
const { ComponentDialog, DialogTurnStatus } = require('botbuilder-dialogs');
/**
* This base class watches for common phrases like "help" and "cancel" and takes action on them
* BEFORE they reach the normal bot logic.
*/
class CancelAndHelpDialog extends ComponentDialog {
async onContinueDialog(innerDc) {
const result = await this.interrupt(innerDc);
if (result) {
return result;
}
return await super.onContinueDialog(innerDc);
}
async interrupt(innerDc) {
if (innerDc.context.activity.text) {
const text = innerDc.context.activity.text.toLowerCase();
switch (text) {
case 'help':
case '?': {
const helpMessageText = 'Show help about prompt: ' + innerDc.activeDialog.id;
await innerDc.context.sendActivity(helpMessageText, helpMessageText, InputHints.ExpectingInput);
return { status: DialogTurnStatus.waiting };
}
case 'cancel':
case 'quit': {
const cancelMessageText = 'Cancelling...';
await innerDc.context.sendActivity(cancelMessageText, cancelMessageText, InputHints.IgnoringInput);
return await innerDc.cancelAllDialogs();
}
}
}
}
}
module.exports.CancelAndHelpDialog = CancelAndHelpDialog;
I want to thank you for using the sample code because you've actually revealed a bug that I've reported here: https://github.com/microsoft/BotBuilder-Samples/issues/2457
The underlying problem here is that the dialogs library has two ways of stacking dialogs. Ordinarily, one dialog gets stacked on top of another dialog like this:
[ CHOICE_PROMPT ]
[ WATERFALL_DIALOG ]
However, component dialogs form a nested dialog stack that stacks inward rather than further upward:
[ REVIEW_SELECTION_DIALOG ]
[ TOP_LEVEL_DIALOG ]
[ MAIN_DIALOG ]
Since not all dialogs are component dialogs, the two ways combine to look like this:
[ CHOICE_PROMPT ]
[ WATERFALL_DIALOG ]
[ REVIEW_SELECTION_DIALOG ]
[ TOP_LEVEL_DIALOG ]
[ MAIN_DIALOG ]
I want to note that the order of this stack is not necessarily what you'd expect if you're used to writing hierarchical lists that look like this (with the most recently added item on the bottom):
MAIN_DIALOG
TOP_LEVEL_DIALOG
REVIEW_SELECTION_DIALOG
WATERFALL_DIALOG
CHOICE_PROMPT
Some people might not consider the second way of stacking actual stacking, since it's a parent-child relationship and not a stack. The reason I'm calling it a second way of stacking here is because of the conceptual similarity to a dialog stack. When you design your bot's dialogs, you have a choice about whether you want each new dialog to be added on top of the existing dialog stack or be nested as a child in an inner dialog stack. The two ways behave similarly because a component dialog ends when its last child dialog ends, so when you pop a dialog off of a stack the stack unravels outwards in much the same way as it unravels downwards. (Remember that new dialogs get added to the top of the stack so "downwards" here means from newer dialogs back to older dialogs, like the stack diagrams I started with.)
The "active dialog" is the dialog at the top of the stack. Since each component dialog has its own dialog set and dialog state and dialog stack and dialog context, each component dialog has a different idea of what the active dialog is. Because the active dialog is defined in terms of a specific dialog stack, when there are multiple dialog stacks the active dialog depends on who you ask.
This didn't cause a problem for you when you were looking for the active dialog in the innermost component dialog. But then you replaced that component dialog's child with the component dialog itself. After that, your (full) stack looked like this:
[ CHOICE_PROMPT ]
[ WATERFALL_DIALOG ]
[ REVIEW_SELECTION_DIALOG ]
[ REVIEW_SELECTION_DIALOG ]
[ TOP_LEVEL_DIALOG ]
[ MAIN_DIALOG ]
When your CancelAndHelpDialog tried to access the active dialog of its inner dialog context, it correctly returned a ReviewSelectionDialog because that was the only dialog on its stack. You wanted to return the choice prompt but that choice prompt was in the dialog stack of the child ReviewSelectionDialog and not the parent ReviewSelectionDialog.
The bug is that you should be replacing the waterfall dialog with itself rather than with the parent component dialog. So it could look like this:
return await stepContext.replaceDialog(WATERFALL_DIALOG, list);
Or like this:
return await stepContext.replaceDialog(this.initialDialogId, list);
Ultimately, this still hasn't answered a question that you may have meant to ask. Since you've seen that problems can arise when you get the active dialog in an intermediate dialog context, you may want a way to get the "real" innermost active dialog. This can be accomplished with some simple recursion:
function getInnermostActiveDialog(dc) {
const child = dc.child;
return child ? getInnermostActiveDialog(child) : dc.activeDialog;
}

concatenate content using loop over array

I have a Bixby capsule in progress that lets users access both free and premium content "packs". Each pack is a file stored in a content/ directory. I want to loop over these files and read them into a variable called entitled_content.
I started from the facts capsule which uses a utility function to search a local file called content.js.
const CONTENT = []
const literature = require("../content/literature")
const enhanced = require("../content/enhanced")
const roosevelt = require("../content/roosevelt")
const ambition = require("../content/ambition")
const chaucer = require ("../content/chaucer")
//const GET_REMOTE = require('./lib/getRemoteContent.js')
var console = require('console')
console.log(roosevelt)
console.log(ambition)
console.log(chaucer)
const entitlements = ["roosevelt", "ambition", "chaucer"]
var entitled_content = []
entitlements.forEach(function (item) {
entitled_content = entitled_content.concat(item)
console.log(item); })
console.log(entitled_content)
What it does is this:
[ { tags: [ 'roosevelt' ],
text: 'Happiness is not a goal; it is a by-product. --Eleanor Roosevelt',
image: { url: 'images/' } } ]
[ { tags: [ 'ambition' ],
text: 'Ambition is but avarice on stilts, and masked. --Walter Savage Landor' } ]
[ { tags: [ 'literature' ],
text: 'A man was reading The Canterbury Tales one Saturday morning, when his wife asked What have you got there? Replied he, Just my cup and Chaucer.' },
{ tags: [ 'literature' ],
text: 'For years a secret shame destroyed my peace-- I\'d not read Eliot, Auden or MacNiece. But now I think a thought that brings me hope: Neither had Chaucer, Shakespeare, Milton, Pope. Source: Justin Richardson.' } ]
roosevelt
ambition
chaucer
[ 'roosevelt', 'ambition', 'chaucer' ]
What I want it to do is to assemble these three files roosevelt, ambition and chaucer into a single array variable entitled_content that will then be searched by the utility function. What's wrong is that this line entitled_content = entitled_content.concat(item) isn't doing what I want it to do, which is to get the entire contents of the file named "item".
Because you wrapped your variable names in quotation marks the program reads them as strings.
Change it from
const entitlements = ["roosevelt", "ambition", "chaucer"]
to
const entitlements = [roosevelt, ambition, chaucer]

Running self-feeding channels in Perl 6

I would like to set up a number of threads operating concurrently on a channel, and every one of those threads should be also feeding the channel. One of the threads would decide when to stop. However, this is the closest I have come to doing that:
use Algorithm::Evolutionary::Simple;
my $length = 32;
my $supplier = Supplier.new;
my $supply = $supplier.Supply;
my $channel-one = $supply.Channel;
my $pairs-supply = $supply.batch( elems => 2 );
my $channel-two = $pairs-supply.Channel;
my $single = start {
react {
whenever $channel-one -> $item {
say "via Channel 1:", max-ones($item);
}
}
}
my $pairs = start {
react {
whenever $channel-two -> #pair {
my #new-chromosome = crossover( #pair[0], #pair[1] );
say "In Channel 2: ", #new-chromosome;
$supplier.emit( #new-chromosome[0]);
$supplier.emit( #new-chromosome[1]);
}
}
}
await (^10).map: -> $r {
start {
sleep $r/100.0;
$supplier.emit( random-chromosome($length) );
}
}
$supplier.done;
This stops after a number of emissions. And it's probably not running concurrently anyway. I am using channels instead of supplies and taps because these are not run concurrently, but asynchronously. I need supplies because I want to have a seudo-channel that takes the elements in pairs, as it's done above; I haven't seen the way of doing that with pure channels.
There is no difference above if I change the supply's emit to channel's send.
So several questions here
Are these react blocks run in different threads? If not, what would be the way of doing that?
Even if they are not, why does it stop even if $pairs is emitting to the channel all the time?
Could I have "batch" channels created automatically from single-item channels?
Update 1: if I eliminate $supplier.done from the end, it will just block. If I create a promise in whenever, one for each read, it just blocks and does nothing.
The answer is here, stripped down to the minimum necessary
my Channel $c .= new;
my Channel $c2 = $c.Supply.batch( elems => 2).Channel;
my Channel $output .= new;
my $count = 0;
$c.send(1) for ^2;
my $more-work = start react whenever $c2 -> #item {
if ( $count++ < 32 ) {
$c.send( #item[1]);
my $sum = sum #item;
$c.send( $sum );
$output.send( $sum );
} else {
$c.close;
}
}
await $more-work;
loop {
if my $item = $output.poll {
$item.say
} else {
$output.close;
}
if $output.closed { last };
}
A second channel that batches the first channel every two elements is used via the creation of a supply from a channel ($c.Supply), batching that supply in batches of two (batch( elems => 2)) and turning it back into a channel. A third channel is created for output.
In order to not exhaust the supply and hang the channel, every second element that is read from the first (and actually, only) channel is put back there. So the second channel that reads in twos is never hanged or waiting for new elements.
An output channel is created for every new element, and an external counter to finish the operation when it's needed; that output channel is read in a non-blocking way, and closed when there's nothing left to read in the last line.
To answer precisely to my original questions:
Yes, they are, only they are stealing elements from each other.
Because the two threads were reading from the same channel. The first one to stumble into an element, reads it.
Yes, by turning channels into supplies, batching them and turning them back into channels. Only bear in mind that they are not copies, they will be sharing the self same elements.

Resources