token = (string) $token; } /** * For the configured bot. * * @return Telegram_Api */ public static function from_settings() { return new self( (string) Settings::get( 'bot_token' ) ); } /** * Call a Bot API method. * * @param string $method Method, e.g. sendMessage. * @param array $params Parameters. * @param int $timeout Seconds. * @return array{ok:bool,code:int,description:string,result:mixed,retry_after:int,network:bool} */ public function call( $method, array $params = array(), $timeout = 15 ) { /** * Base URL of the Bot API. Changed only to point at a self-hosted Bot API server. * * @param string $base Default https://api.telegram.org. */ $base = (string) apply_filters( 'orderchirp_telegram_api_base', 'https://api.telegram.org' ); $response = wp_remote_post( rtrim( $base, '/' ) . '/bot' . $this->token . '/' . $method, array( 'timeout' => $timeout, 'headers' => array( 'Content-Type' => 'application/json' ), 'body' => wp_json_encode( $params ), ) ); if ( is_wp_error( $response ) ) { return array( 'ok' => false, 'code' => 0, 'description' => self::redact( $response->get_error_message(), $this->token ), 'result' => null, 'retry_after' => 0, 'network' => true, ); } return self::parse_response( (int) wp_remote_retrieve_response_code( $response ), (string) wp_remote_retrieve_body( $response ) ); } /** * Turn an HTTP answer into the result array. A body that is not Telegram's JSON is a failure. * * @param int $code HTTP status. * @param string $body Body. * @return array{ok:bool,code:int,description:string,result:mixed,retry_after:int,network:bool} */ public static function parse_response( $code, $body ) { $data = json_decode( $body, true ); if ( ! is_array( $data ) || ! array_key_exists( 'ok', $data ) ) { return array( 'ok' => false, 'code' => $code, 'description' => 'Unexpected answer from Telegram (HTTP ' . $code . ')', 'result' => null, 'retry_after' => 0, 'network' => $code >= 500 || 0 === $code, ); } $ok = true === $data['ok']; $retry_after = 0; if ( isset( $data['parameters']['retry_after'] ) ) { $retry_after = max( 1, (int) $data['parameters']['retry_after'] ); } return array( 'ok' => $ok, 'code' => isset( $data['error_code'] ) ? (int) $data['error_code'] : $code, 'description' => isset( $data['description'] ) ? (string) $data['description'] : '', 'result' => isset( $data['result'] ) ? $data['result'] : null, 'retry_after' => $retry_after, 'network' => ! $ok && $code >= 500, ); } /** * Whether a failure means the person blocked the bot or deleted their account, so the chat * must be forgotten rather than retried. * * @param array{ok:bool,code:int,description:string} $result Result of call(). * @return bool */ public static function chat_is_gone( array $result ) { if ( $result['ok'] ) { return false; } if ( 403 === (int) $result['code'] ) { return true; } return 400 === (int) $result['code'] && false !== stripos( (string) $result['description'], 'chat not found' ); } /** * Remove the bot token from text that may be logged or shown (an HTTP error message can * contain the full request URL). * * @param string $text Text. * @param string $token Token. * @return string */ public static function redact( $text, $token ) { $text = (string) $text; if ( '' !== (string) $token ) { $text = str_replace( (string) $token, '***', $text ); } return (string) preg_replace( '/bot\d{5,16}:[A-Za-z0-9_-]{30,64}/', 'bot***', $text ); } /** * Send a plain-text message. * * @param string|int $chat_id Chat. * @param string $text Text. * @param array|null $buttons Optional rows of URL buttons: [[['text'=>..,'url'=>..]]]. * @return array{ok:bool,code:int,description:string,result:mixed,retry_after:int,network:bool} */ public function send_message( $chat_id, $text, $buttons = null ) { $params = array( 'chat_id' => (string) $chat_id, 'text' => (string) $text, 'link_preview_options' => array( 'is_disabled' => true ), ); if ( $buttons ) { $params['reply_markup'] = array( 'inline_keyboard' => $buttons ); } return $this->call( 'sendMessage', $params ); } }