Core: Problem with cron and runCommands

Created on 20 Jul 2017  路  16Comments  路  Source: php-telegram-bot/core

Required Information

  • Operating system: CentOS 7.3.1611
  • PHP version: 7.1.2 (64bit); apache2handler; Linux
  • PHP Telegram Bot version: 0.46.0
  • Using MySQL database: yes
  • MySQL version: 5.5.52-MariaDB
  • Update Method: Webhook
  • Self-signed certificate: no
  • RAW update (if available):
    Longman\TelegramBot\Entities\ServerResponse::__set_state(array( 'ok' => false, 'error_code' => 403, 'description' => 'Forbidden: bot can\'t send messages to bots', 'raw_data' => array ( 'ok' => false, 'error_code' => 403, 'description' => 'Forbidden: bot can\'t send messages to bots', ), 'bot_username' => '%mybotname%', )),

    Steps to reproduce

This error produce when execute cron.php from example repository. I tried truncate users and chat tables, but no effects.

Most helpful comment

/show commands gets triggered by user manually, so there is incoming update containing his user_id so the bot knows who reply to.

What you want to do is write seperate command that iterates over all users in the database, checks if they got notification and then sends a message to them using user_id from the database row.

All 16 comments

It means you try to run a command that sends a reply back, while coding your commands for usage with cron you must prevent sending message to itself ($this->telegram->getBotId()).

The example probably runs echo command which messages back whoever executed it.

I tried sent only /help command.
With your fix it didn't work.

if($this->telegram->getBotId() == $chat_id) {
    return false;
}

Message to me wasn't sent.

Are you using the cron.php and HelpCommand.php from the example-bot repository?
If not, could you post the contents (without any credentials) of your cron.php?

@noplanman yes, from example repository.
HelpCommand.php

<?php
/**
 * This file is part of the TelegramBot package.
 *
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Longman\TelegramBot\Commands\UserCommands;

use Longman\TelegramBot\Commands\Command;
use Longman\TelegramBot\Commands\UserCommand;
use Longman\TelegramBot\Request;
use Longman\TelegramBot\Helpers;

/**
 * User "/help" command
 */
class HelpCommand extends UserCommand
{
    /**
     * @var string
     */
    protected $name = 'help';

    /**
     * @var string
     */
    protected $description = 'Show list of available commands';

    /**
     * @var string
     */
    protected $usage = '/help or /help <command>';

    /**
     * @var string
     */
    protected $version = '1.2.0';

    public $show_in_help = false;

    /**
     * @inheritdoc
     */
    public function execute()
    {
        $message     = $this->getMessage();
        //$chat_id     = $message->getFrom()->getId();
        $chat_id     = $message->getChat()->getId();
        $command_str = trim($message->getText(true));

        $data = [
            'chat_id'    => $chat_id,
            'parse_mode' => 'markdown',
        ];
        list($all_commands, $user_commands, $admin_commands) = $this->getUserAdminCommands();

        // If no command parameter is passed, show the list.
        if ($command_str === '') {
            $data['text'] = 'Welcome!' . PHP_EOL;
            $data['text'] .= PHP_EOL . '*Commands List*:' . PHP_EOL;
            foreach ($user_commands as $user_command) {
                $data['text'] .= '/' . $user_command->getName() . ' - ' . $user_command->getDescription() . PHP_EOL;
            }

            if (count($admin_commands) > 0) {
                $data['text'] .= PHP_EOL . '*Admin Commands List*:' . PHP_EOL;
                foreach ($admin_commands as $admin_command) {
                    $data['text'] .= '/' . $admin_command->getName() . ' - ' . $admin_command->getDescription() . PHP_EOL;
                }
            }

            $data['text'] .= PHP_EOL . 'For exact command help type: /help <command>';
            // Helpers::dump($data);
            if($this->telegram->getBotId() == $chat_id) {
                return false;
            }

            return Request::sendMessage($data);
        }

        $command_str = str_replace('/', '', $command_str);
        if (isset($all_commands[$command_str])) {
            $command      = $all_commands[$command_str];
            $data['text'] = sprintf(
                '*Command:* %s (v%s)' . PHP_EOL .
                '*Description:* %s' . PHP_EOL .
                '*Usage:* %s',
                $command->getName(),
                $command->getVersion(),
                $command->getDescription(),
                $command->getUsage()
            );

            return Request::sendMessage($data);
        }

        $data['text'] = 'No help available: Command /' . $command_str . ' not found';

        return Request::sendMessage($data);
    }

    /**
     * Get all available User and Admin commands to display in the help list.
     *
     * @return Command[][]
     */
    protected function getUserAdminCommands()
    {
        // Only get enabled Admin and User commands that are allowed to be shown.
        /** @var Command[] $commands */
        $commands = array_filter($this->telegram->getCommandsList(), function ($command) {
            /** @var Command $command */
            return !$command->isSystemCommand() && $command->showInHelp() && $command->isEnabled();
        });

        $user_commands = array_filter($commands, function ($command) {
            /** @var Command $command */
            return $command->isUserCommand();
        });

        $admin_commands = array_filter($commands, function ($command) {
            /** @var Command $command */
            return $command->isAdminCommand();
        });

        ksort($commands);
        ksort($user_commands);
        ksort($admin_commands);

        return [$commands, $user_commands, $admin_commands];
    }
}

cron.php

<?php

// Load composer
require_once __DIR__ . '/vendor/autoload.php';
include_once './Helpers.php'; // its dump function from wiki

// Add you bot's API key and name
$bot_api_key  = '';
$bot_username = '';

// Define all IDs of admin users in this array (leave as empty array if not used)
$admin_users = [
    %my_id%
];

// Define all paths for your custom commands in this array (leave as empty array if not used)
$commands_paths = [
    __DIR__ . '/Commands/',
];

// Enter your MySQL database credentials
$mysql_credentials = [
    'host'     => '',
    'user'     => '',
    'password' => '',
    'database' => '',
];

$commands = [
    '/help'
];

try {
    // Create Telegram API object
    $telegram = new Longman\TelegramBot\Telegram($bot_api_key, $bot_username);

    // Add commands paths containing your custom commands
    $telegram->addCommandsPaths($commands_paths);

    // Enable admin users
    $telegram->enableAdmins($admin_users);

    // Enable MySQL
    $telegram->enableMySql($mysql_credentials, $bot_username . '_');

    // Logging (Error, Debug and Raw Updates)
    Longman\TelegramBot\TelegramLog::initErrorLog(__DIR__ . "/{$bot_username}_cron_error.log");

    $telegram->enableLimiter();
    // Run user selected commands
    $telegram->runCommands($commands); //
    //Longman\TelegramBot\Helpers::dump($telegram);
} catch (Longman\TelegramBot\Exception\TelegramException $e) {
    // Silence is golden!
    //echo $e;
    // Log telegram errors
    Longman\TelegramBot\TelegramLog::error($e);
} catch (Longman\TelegramBot\Exception\TelegramLogException $e) {
    // Silence is golden!
    // Uncomment this to catch log initialisation errors
    echo $e;
}

@catsAND You must understand cron is meant for commands doing some kind of scheduled tasks.
Commands run with runCommands() are executed as the bot itself, and because bot cannot message itself it will return errors.

In this case you replace sendMessage() part with:

if($this->getTelegram()->getBotId() != $chat_id) {
     return Request::emptyResponse();
} else {
     return Request::sendMessage($data);
}

(the code you pasted earlier does totally different thing, it makes it so that command only answers to bot)

I didnt understand what you mean, but i need every hour send text message. How can i do that?

bot cannot message itself it will return errors

I have if with return false; to prevent this.

With your code i get this error:

 Longman\TelegramBot\Entities\ServerResponse::__set_state(array(
     'ok' => false,
     'error_code' => 403,
     'description' => 'Forbidden: bot can\'t send messages to bots',
     'raw_data' => 
    array (
      'ok' => false,
      'error_code' => 403,
      'description' => 'Forbidden: bot can\'t send messages to bots',
    ),
     'bot_username' => '',
  )

How i understand bot cannot send message via cron to other people?

Check my code, especially notice != statement where in your code it's == which will send only if ID equals bot's ID.

Also i don't think you should return false in execute() in commands, use return Request::emptyResponse() or return parent::execute().

_Whopsie mobile github :/_

Yes, i tried with your code snippet execute command and telegram return error that i wrote above.

@jacklul, I think you got it mixed up in your comment, which only sends if the bot ID is the same as the chat ID. It should be === in your case, or the clauses swapped.

@catsAND If you really just want to send a message to a user via cron, consider a much much simpler solution that doesn't even require this library:

#!/usr/bin/env php
<?php
// sendMessage.php
// Usage: ./sendMessage.php <chat_id> "<text>"

$bot_token = '<api_key>';
($chat_id = array_slice($argv, 1, 1)[0] ?? null) || die('Chat ID required' . PHP_EOL);
($text    = trim(implode(' ', array_slice($argv, 2))) ?? null) || die('Message text required' . PHP_EOL);

file_get_contents("https://api.telegram.org/bot{$bot_token}/sendMessage?chat_id={$chat_id}&text=" . urlencode($text));

This will send a message from your bot to the user id passed as a parameter, for example:
./sendMessage <your-user-id> "Yep, it's working!"

Alternatively, you'd need to write a custom command, for example /send <chat_id> <text> and use that in your cron.php file.
execute() method of admin command SendCommand.php would be like this:

public function execute()
{
    $text_parts = explode(' ', trim($this->getMessage()->getText(true)));

    return Request::sendMessage([
        'chat_id' => array_shift($text_parts),
        'text'    => implode(' ', $text_parts),
    ]);
}

Does this help?

Remember, as @jacklul mentioned, executing commands with cron, is literally the bot sending the message. So having the bot send /help is useless, because it doesn't do anything. Cron should be used for tasks that are separate from the user, like the /cleanup command, or like above, to send a message periodically, like a new update for example.

@noplanman thanks. Now i understand for what purpose cron.php in example repository.
Its bad that can't using framework in cron.php.

Its bad that can't using framework in cron.php.

Could you please explain this? Of course you can use the framework with cron, it just needs to be coded.

Also, maybe you could elaborate a bit more on your use case so we can help find a better solution if required?

Could you please explain this?

If write cron.php without framework, in my case, i need every changes duplicate in command that i sending.
And of course unit tests. I need write few additional test.

Also, maybe you could elaborate a bit more on your use case so we can help find a better solution if required?

Im developing notification bot. And every hour bot execute command /show that tell user about new notifications. Notification storing in db.

If simple command looks like this:

User: /show
Bot: You have 1 notification.
1. Notification about meetup.
To see additional information about notification use /show <id>

I found only one way how i can use cron with framework to execute command. I using function processUpdate with new created update entity. But in this variant is one con. I need generate uniq id for every message avoid SQL error.

/show commands gets triggered by user manually, so there is incoming update containing his user_id so the bot knows who reply to.

What you want to do is write seperate command that iterates over all users in the database, checks if they got notification and then sends a message to them using user_id from the database row.

@jacklul yes, i do so.

My cron code snippet:

    $pdo = Longman\TelegramBot\DB::getPdo();

    $sth = $pdo->prepare("SELECT `chat_id` FROM `table_id` GROUP BY `chat_id`;");
    $sth->execute();
    $users = $sth->fetchAll(\PDO::FETCH_COLUMN);

    foreach($users as $chat_id) {
        $update = new Longman\TelegramBot\Entities\Update(
            [
                'update_id' => time() + mt_rand(0, 100),
                'message' => [
                    'message_id' => 0,
                    'from' => [
                        'id' => $chat_id,
                        'first_name' => 'cron',
                        'username' => 'cron',
                    ],
                    'date' => time(),
                    'chat' => [
                        'id' => $chat_id,
                        'type' => 'private',
                    ],
                    'text' => '/show',
                ],
            ]
        );
        $telegram->processUpdate($update);
    }

write seperate command

How can i create separate command that execute /show command without duplicating code?

I am played with namespaces, extends and $telegram->executeCommand() but didnt understand how right using it.

Best thing would be to extract the /show functionality to a separate class that you can call anywhere. Then, in your cron, simply loop the user list and do a Request::sendMessage(...) for each user that has notifications. I think it doesn't make sense to create fake updates for all of these messages.

On the topic, just use -1 for update_id, otherwise there will probably be a problem with duplicate entries at some point.

Thanks you for your help.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nesttle picture nesttle  路  4Comments

Recouse picture Recouse  路  3Comments

NabiKAZ picture NabiKAZ  路  4Comments

smaznet picture smaznet  路  4Comments

dorcu picture dorcu  路  3Comments