botman / driver-twitter

BotMan Twitter Driver
MIT License
7 stars 6 forks source link

Twitter Driver: Simple echo chatbot going into infinite loop #2

Open dimplevador opened 5 years ago

dimplevador commented 5 years ago

Description:

A simple Echo Chatbot for Twitter Direct Messages is going into infinite loop.

I have narrowed down the problem to this: When user DM's, webhook receives event: "message_create". When webhook posts reply, twitter sends delivery report (Response) with the following json, notice the event "message_create". The event name is same for receiving actual user message & also for bot message delivery report :

{
"event": {
  "type": "message_create",
  "message_create": {
    "target": {
      "recipient_id": "RECIPIENT_USER_ID"
    },
    "message_data": {
      "text": "Hello World!",
    }
  }
}
}

I have not been able to find difference between incoming message & delivery report json. Can someone please help me out with that?

The function that needs to be fixed in Twitter Driver is getMessages() on On line #68 - https://github.com/botman/driver-twitter/blob/master/src/TwitterDriver.php

Thank you very much in advance.

Steps To Reproduce:

Twitter webhook:

<?php
    require __DIR__ . '/vendor/autoload.php';

    use BotMan\BotMan\BotMan;
    use BotMan\BotMan\BotManFactory;
    use BotMan\BotMan\Drivers\DriverManager;

    use BotMan\BotMan\Cache\DoctrineCache;
    use Doctrine\Common\Cache\FilesystemCache;

    $config = [
        'twitter' => [
            'consumer_key' => '****',
            'consumer_secret' => '****',
            'token'=> '***',
            'token_secret'=> '*****',
        ]
    ];

    if(isset($_GET['crc_token'])) {
        $signature = hash_hmac('sha256', $_REQUEST['crc_token'], $config['twitter']['consumer_secret'], true);
        $response['response_token'] = 'sha256='.base64_encode($signature);
        print json_encode($response);
    }
    else{
        DriverManager::loadDriver(\BotMan\Drivers\Twitter\TwitterDriver::class);

        $doctrineCacheDriver = new FilesystemCache(__DIR__);
        $botman = BotManFactory::create($config, new DoctrineCache($doctrineCacheDriver));

        // Give the bot something to listen for.
        $botman->hears('(.*)', function (BotMan $bot, $text) {
            $bot->reply("Echo: ".$text);
        });

        // Start listening
        $botman->listen();
    }
?>
christophrumpel commented 5 years ago

I haven't used the Twitter driver yet. Is still not working or did you found a solution?

dimplevador commented 5 years ago

I haven't used the Twitter driver yet. Is still not working or did you found a solution?

Thank you for replying.

The original code is still not working. I found a solution that works for my situation. Taking the current TwitterDriver as base i wrote a custom driver - overriding the problematic functions that i needed. I was not able to resolve all the problems of the driver.

sololance commented 5 years ago

Hi,

This can be fixed by checking user id of sender

<?php
$botman->hears('(.*)', function (BotMan $bot, $text) {

    // Get user
    $user = $bot->getUser();

    // Get user id
    $user_id = $user->getId();

    // Check if twitter user id is not bot id
    if($user_id != $twitter['user_id']){
        $bot->reply("Echo: ".$text);
    }
});

Maybe we can add new parameter to config "user_id", so when twitter send back message we can detect it from "recipient_id" and then pass message as false?

What do you think @mpociot ?

Thanks.

dimplevador commented 5 years ago

I had to make changes to two functions in TwitterDriver.php for it to work. It works for my requirement, don't know whether it will work for all the requirements.

    /**
     * Determine if the request is for this driver.
     *
     * @return bool
     */
    public function matchesRequest()
    {
        if (isset($this->headers['x-twitter-webhooks-signature'])) {

            $signature = $this->headers['x-twitter-webhooks-signature'][0];
            $hash = hash_hmac('sha256', json_encode($this->payload->all()), $this->config->get('consumer_secret'), true);

            if( $signature === 'sha256='.base64_encode($hash) ){

                //Check whether the incoming message is just delivery report of the previously sent message.
                if( $this->payload->has('direct_message_events') ){
                    if( $this->payload->get('for_user_id') != $this->payload->get('direct_message_events')[0]['message_create']['sender_id'] ) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    /**
     * Retrieve User information.
     * @param \BotMan\BotMan\Messages\Incoming\IncomingMessage $matchingMessage
     * @return User
     */
    public function getUser(IncomingMessage $matchingMessage)
    {
        $sender_id = $matchingMessage->getSender();

        $user = Collection::make($this->payload->get('users'))->first(function ($user) use ($sender_id) {
            return $user['id'] === $sender_id;
        });

        return new User($user['id'], null, null, $user['name'], $user);
    }
sololance commented 5 years ago

@dimplevador well, changes made by yourself in the source code isn't good choice. When library will be updated you'll need to follow up changes.

Does my example works as expected for you?

I still think that we don't have to make up any changes because it's really useful feature for some kind of logging (messages can be sent from any app integrated with twitter, so checking source app is also neat !)

dimplevador commented 5 years ago

@dimplevador well, changes made by yourself in the source code isn't good choice. When library will be updated you'll need to follow up changes.

Does my example works as expected for you?

I still think that we don't have to make up any changes because it's really useful feature for some kind of logging (messages can be sent from any app integrated with twitter, so checking source app is also neat !)

@sololance :) Thank you for taking time to checkout the problem. Your solution doesn't work though. The code now does not go into finite loop but it still makes two additional posts for each call - one to twitter and one to self.

I didn't make any change to the source code. I just wrote a custom driver by extending TwitterDriver and overrode the two functions - according to my requirements.

If i am using the code without any changes, it goes into infinite loop with just one simple echo call.

salmanyaqoobin commented 5 years ago

Can someone please help me, to send get started information for botman-twitter driver.

dimplevador commented 5 years ago

Can someone please help me, to send get started information for botman-twitter driver.

@salmanyaqoobin The existing source code is faulty. You will have to create a custom driver by extending the existing TwitterDriver and override two methods: matchesRequest() and getUser(IncomingMessage $matchingMessage). You can find the new code for both these methods in the above discussion.

Documentation for extending / customizing driver https://botman.io/2.0/drivers

Please note that this modification works for my purpose. I am not sure whether it will work for you or not.

salmanyaqoobin commented 5 years ago

@dimplevador Thanks for the Quick Reply, your two functions even solve the loop messaging issue. Rest of all the work to get ready the chatbot for twitter is giving me hard time, but eventually i got manage to figure out all the problem. I created following new features:

  1. Welcome Message with image.(If someone can enhance it to command console)
  2. Add button Template Extension
  3. Add Button Element Extension

Welcome Message with image Code (browser base controller approach) `public function upload(Request $request) { $welcome_message = $request->get('welcome_message'); $media_file = $request->file('media');

    $connection = new TwitterOAuth(
        config('botman.twitter.consumer_key'),
        config('botman.twitter.consumer_secret'),
        config('botman.twitter.token'),
        config('botman.twitter.token_secret')
    );

    $content = $connection->get("account/verify_credentials");

    if(!isset($content->errors)){
        $media = $connection->upload('media/upload', [
            //'media' => rawurldecode($url),
            'media' => $media_file,
            //'media_data'=>$imageBase64,
            'media_category'=>'dm_image',
            'media_type'=>'dm_image',
            'shared'=>true,
        ], true);

        if (isset($media->errors)) {
            $this->error('Unable to upload media file.');
            dump($media);
        } else {
            echo 'Your media has been uploaded to twitter';
            //dump($media);
            if(isset($media->media_id)){
                echo "media uploaded";
                $payload = [
                    'welcome_message' => [
                        'name' => 'in-welcome-message',
                        'message_data' => [
                            'text' => $welcome_message,
                            'attachment'=>[
                                "type"=> "media",
                                "media"=>[
                                    "id"=>$media->media_id
                                ]
                            ]
                        ],
                    ]
                ];
                $welcome_messages = $connection->post("direct_messages/welcome_messages/new", $payload, true);
                if(!isset($welcome_messages->errors) && isset($welcome_messages->welcome_message->id)){
                    echo "Welcome message has been created";
                    $payload = [
                        'welcome_message_rule' => [
                            'welcome_message_id' => $welcome_messages->welcome_message->id,
                        ]
                    ];
                    $welcome_messages_rule = $connection->post("direct_messages/welcome_messages/rules/new", $payload, true);
                    dump($welcome_messages_rule);
                }
                dump($welcome_messages);
            }
            exit;
        }
    }

}`
dimplevador commented 5 years ago

@salmanyaqoobin I am glad it worked out for you :)

Thank you for sharing the welcome message with image extension function.