Skip to content

Conversations Reloaded #227

Open
Open
@noplanman

Description

@noplanman

I've been putting a lot of thought into redesigning the conversations and how we could make it easier for users to create totally customised conversations.

Here's what I've come up with so far. It's completely up for discussion. Let's collect ideas and suggestions here to come up with a great solution! 😃
The class names are open to be changed to something better if anyone has any ideas.

Also note, this is more like a "what i would like to be able to do as a user", as opposed to a "this is simple to implement". So there is no real code for this just yet, meaning that we'd need to figure out how best to implement the final solution.

I hope you understand my current solution as it's present in my mind. Just ask if something is unclear and I'll update this issue.

Here are a list of required classes so far:

ConversationItemType class

An enum of different entity types. It would make sense to not have this class but instead have a general abstract class as an enum for all entity types. What I mean is, have a class that can be used anywhere in the library (for example EntityType), to compare for entity types. So instead of giving the message a $type string, it would get a specific const of an enum, e.g. EntityType::DOCUMENT.

e.g.

abstract class ConversationItemType // or rather "EntityType"
{
  const TEXT     = 1;
  const AUDIO    = 2;
  const DOCUMENT = 4;
  const PHOTO    = 8;
  const STICKER  = 16;
  const VIDEO    = 32;
  const VOICE    = 64;
  const CONTACT  = 128;
  const LOCATION = 256;
  const VENUE    = 512;
  const ANY      = 1023; // Next bit - 1
}

ConversationItem class

Each part of the conversation is an object of this class.

It's made up of 2 core methods:

  • The initial text that gets outputted when the item is called
  • The process that handles the reply and decides what data should be saved for the current item

And a bunch of extra ones that help control the conversation (see below)

To enforce a clean splitting up of the code, all necessary parts are pointers to callback functions.

Callback functions would be for the initial output, the handling of the reply, the custom "invalid type" text, the custom check if a valid reply has been made by the user, etc. (in case there are any others)

Each item has a unique id, so that the state of the conversation can be remembered and the input of each item can be saved properly.

Additionally, we assign the next item to an already existing item, thus making a tree of items. This "tree" is the flow of the conversation.

e.g. Create an item that asks for the name and another one that asks for the age. We then assign the one asking for the age, to the one asking for the name. This way, the conversation class knows which item comes next.

conversation-simple

Expanding on this, would be the ability to define multiple next items, depending on the input by the user.

e.g. Conversation item says: "Send a photo or document"
We define the next conversation item if a photo is sent, and a different one if a document is sent.

conversation-complex

Each item also has an array of flags that tell the item which reply types are accepted

e.g. When asking the user for a document, the "Document" flag would be set, letting the conversation item know if the correct input has been made by the user.

If not, a custom "wrong input format" output could be defined and the initial text is outputted to the user.

Conversation class

This class is the one that manages the whole conversation, interacting with the database, fetching and storing data etc.
(similar to the way it is now)

The top-most conversation item gets added to the conversation object to start the conversation.

Additional necessary changes

Add a default __toString() method to all entities, that would return a default string that makes sense.

e.g. $location->__toString() would return: Latitude: <lat> - Longitude: <lng>

A simple pseudo-code example could be (inside a command):

<?php

function execute()
{
    $conversation = Conversation::load($user_id, $chat_id, ...);

    $accepted_types_1 = ConversationItemTypes::DOCUMENT | ConversationItemTypes::PHOTO;
    $root_conversation_item = new ConversationItem(
        'doc_or_photo', // Unique ID.
        [&$this, 'init_callback_1'], // Either a proper callback function...
        [&$this, 'response_callback_1'], // Either a proper callback function...
        $accepted_types_1
    );
    $root_conversation_item->setInvalidTypeResponse([&$this, 'invalid_type_response_callback_1']); // Either a proper callback function...

    $accepted_types_2 = ConversationItemTypes::LOCATION;
    $location_conversation_item = new ConversationItem(
        'loc',
        'Please specify a location', // ...or just text.
        [&$this, 'response_callback_2'],
        $accepted_types_2
    );
    $location_conversation_item->setInvalidTypeResponse('Only a location object allowed...'); // ...or just text.

    $anything_conversation_item = new ConversationItem(
        'anything',
        'Please send anything, really...',
        'Anything received, no idea what though :-/', // ...or just text. If null, default __toString() of $response_message (would need to be defined first!)
        ConversationItemTypes::ANY
    );

    // Add next items.
    // If a photo is passed, next item is $location_conversation_item...
    $root_conversation_item->addNextItem($location_conversation_item, ConversationItemType::PHOTO);
    // else $anything_conversation_item.
    $root_conversation_item->addNextItem($anything_conversation_item, ConversationItemType::ANY);

    // Add the root conversation item.
    $conversation->addRootItem($root_conversation_item);

    // Start the conversation. This checks which state we're in to skip ahead to the proper place if necessary.
    $conversation->start();
}


function init_callback_1($conversation, $conversation_item)
{
    return 'Please upload a document or photo';
}
function response_callback_1($conversation, $conversation_item, $response_message)
{
    $doc   = $response_message->getDocument();
    $photo = $response_message->getPhoto();
    $data  = null;
    if ($doc) {
        $data = $doc->getFileId();
    } elseif ($photo) {
        $data = end($photo)->getId();
    }
    return $data;
}
function invalid_type_response_callback_1($conversation, $conversation_item, $response_message)
{
    $response = 'Either a document or photo please, nothing else.';
    if ($response_message->getLocation()) {
        $response .= "\n" . 'Especially NOT a Location!';
    }
    return $response;
}

function response_callback_2($conversation, $conversation_item, $response_message)
{
    $loc = $response_message->getLocation();
    return 'Lat: ' . $loc->getLatitude() . ' - Lng: ' . $loc->getLongitude();
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions