diff --git a/README.md b/README.md index 69cbc7e..d15048d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,46 @@ -# DataScience -Curso DataScience +# Bitrix24 Open Channel Auto Bot (Local App) + +Pequeno app local em PHP para registrar um bot que aparece na listagem de bots do Open Channel e responde automaticamente: +- Ao iniciar uma conversa +- Quando o usuário envia uma mensagem + +## Estrutura +- `public/index.php`: UI de configuração +- `public/save-config.php`: salva mensagens padrão +- `public/install.php`: endpoint de instalação (chama imbot.bot.add/imbot.register) +- `public/events.php`: webhooks de eventos do bot +- `src/*`: utilitários e integrações +- `data/config.json`: mensagens padrão +- `data/state.json`: estado (ex.: BOT_ID) + +## Requisitos +- PHP 8.0+ +- Extensão cURL habilitada +- Servir o diretório `public` (ex.: `php -S 0.0.0.0:8080 -t public`) + +## Passos de instalação no Bitrix24 +1. Inicie o servidor local expondo o `public` (ou use um túnel HTTPS, ex.: ngrok) + ```bash + php -S 0.0.0.0:8080 -t public + ``` +2. Tenha uma URL pública HTTPS, por exemplo via ngrok: + ```bash + ngrok http http://localhost:8080 + ``` +3. Em Bitrix24, crie um aplicativo local (Marketplace → Meus Aplicativos → Adicionar Aplicativo): + - URL de instalação: `https://SEU_DOMINIO/install.php` + - Nome: "OpenChannel Auto Bot" + - Permissões: ImBot, ImOpenLines +4. Instale o app e autorize. O endpoint de instalação registrará o bot com `OPENLINE=Y` e apontará os eventos para `https://SEU_DOMINIO/events.php`. +5. Em Contact Center / Open Channels, adicione o bot à fila/roteamento conforme necessário. Ele aparecerá na lista de bots disponíveis. + +## Teste +- Inicie um chat no Open Channel; o bot deve enviar a mensagem de boas-vindas (configurável) +- Envie mensagens; o bot responde com a mensagem padrão + +## Configuração +Acesse `https://SEU_DOMINIO/` para editar as mensagens padrão. + +## Notas +- Em algumas contas a API antiga `imbot.register` pode ser necessária; o código tenta ambas. +- Para remover o bot, exclua-o no Bitrix24; o webhook `ONIMBOTDELETE` limpa o `data/state.json`. diff --git a/data/config.json b/data/config.json new file mode 100644 index 0000000..2efb61e --- /dev/null +++ b/data/config.json @@ -0,0 +1 @@ +{\n "default_on_start": "Olá! Somos o suporte, já vamos te atender.",\n "default_on_message": "Recebemos sua mensagem e responderemos em breve."\n}\n \ No newline at end of file diff --git a/public/events.php b/public/events.php new file mode 100644 index 0000000..ca0f592 --- /dev/null +++ b/public/events.php @@ -0,0 +1,55 @@ + $req]); + + $event = $req['event'] ?? ''; + $auth = $req['auth'] ?? $req; + $config = Config::load(); + $state = Config::loadState(); + $botId = (int)($state['bot_id'] ?? 0); + + // Normalize payload fields across events + $params = $req['params'] ?? $req['data'] ?? $req; + + if ($event === 'ONIMBOTJOINCHAT' || $event === 'ONIMBOTWELCOME') { + // Chat started. DIALOG_ID provided via params['DIALOG_ID'] or ['dialog']['id'] + $dialogId = $params['DIALOG_ID'] ?? ($params['dialog']['id'] ?? null); + if ($dialogId && $botId) { + Bitrix::sendBotMessage($auth, $botId, (string)$dialogId, (string)($config['default_on_start'] ?? 'Olá!')); + } + echo json_encode(['ok' => true]); + return; + } + + if ($event === 'ONIMBOTMESSAGEADD') { + // User sent message + $dialogId = $params['DIALOG_ID'] ?? ($params['message']['chat_id'] ?? $params['message']['dialog_id'] ?? null); + if ($dialogId && $botId) { + Bitrix::sendBotMessage($auth, $botId, (string)$dialogId, (string)($config['default_on_message'] ?? 'Obrigado pela mensagem!')); + } + echo json_encode(['ok' => true]); + return; + } + + if ($event === 'ONIMBOTDELETE') { + // Clear state + Config::saveState([]); + echo json_encode(['ok' => true]); + return; + } + + echo json_encode(['ok' => true, 'ignored' => $event]); +} catch (Throwable $e) { + http_response_code(500); + Logger::log(['error' => $e->getMessage()]); + echo json_encode(['ok' => false, 'error' => $e->getMessage()]); +} diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..9fce82f --- /dev/null +++ b/public/index.php @@ -0,0 +1,50 @@ + + + + + + + Bitrix24 Open Channel Bot - Config + + + +

Configuração do Bot de Open Channel

+

Defina as respostas padrão que o bot enviará ao iniciar um chat e ao receber mensagens.

+
+ + + +
+
+

Registro do Bot

+

Use o endpoint /install.php como URL de instalação no Bitrix24, e os webhooks de eventos apontam para /events.php.

+ + + diff --git a/public/install.php b/public/install.php new file mode 100644 index 0000000..b4ca91f --- /dev/null +++ b/public/install.php @@ -0,0 +1,26 @@ + 'install', 'result' => $result]); + echo json_encode(['ok' => true, 'result' => $result]); +} catch (Throwable $e) { + http_response_code(500); + Logger::log(['error' => $e->getMessage()]); + echo json_encode(['ok' => false, 'error' => $e->getMessage()]); +} diff --git a/public/save-config.php b/public/save-config.php new file mode 100644 index 0000000..32ee937 --- /dev/null +++ b/public/save-config.php @@ -0,0 +1,24 @@ + false, 'message' => 'JSON inválido']); + exit; +} + +$config = [ + 'default_on_start' => trim((string)($data['default_on_start'] ?? '')), + 'default_on_message' => trim((string)($data['default_on_message'] ?? '')), +]; + +Config::save($config); +Logger::log(['event' => 'config_saved', 'config' => $config]); + +header('Content-Type: application/json'); +echo json_encode(['ok' => true, 'message' => 'Configurações salvas']); diff --git a/src/Bitrix.php b/src/Bitrix.php new file mode 100644 index 0000000..6c77367 --- /dev/null +++ b/src/Bitrix.php @@ -0,0 +1,112 @@ + true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query($postFields), + CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'], + CURLOPT_TIMEOUT => 15, + ]); + $resp = curl_exec($ch); + $errno = curl_errno($ch); + $error = curl_error($ch); + curl_close($ch); + if ($errno) { + throw new \RuntimeException('Erro HTTP: ' . $error); + } + $data = json_decode($resp ?: 'null', true); + return is_array($data) ? $data : ['raw' => $resp]; + } + + public static function registerBot(array $auth, string $baseUrl, array $overrides = []): array + { + $state = Config::loadState(); + $code = $state['bot_code'] ?? ('openline_autobot_' . substr(md5(($overrides['seed'] ?? '') . microtime(true)), 0, 8)); + $eventsUrl = rtrim($baseUrl, '/') . '/events.php'; + + $params = [ + 'CODE' => $code, + 'TYPE' => 'B', + 'OPENLINE' => 'Y', + 'EVENT_MESSAGE_ADD' => $eventsUrl, + 'EVENT_WELCOME_MESSAGE' => $eventsUrl, + 'EVENT_BOT_DELETE' => $eventsUrl, + 'FIELDS' => [ + 'NAME' => $overrides['NAME'] ?? 'OpenChannel Auto Bot', + 'COLOR' => $overrides['COLOR'] ?? 'AQUA', + ], + ]; + + $result = self::call($auth, 'imbot.bot.add', $params); + if (isset($result['error'])) { + // Try alternative legacy method name if available + try { + $result = self::call($auth, 'imbot.register', $params); + } catch (\Throwable $e) { + // keep original + } + } + + if (!empty($result['result'])) { + $botId = (int)$result['result']; + $state['bot_id'] = $botId; + $state['bot_code'] = $code; + Config::saveState($state); + } + return $result; + } + + public static function sendBotMessage(array $auth, int $botId, string $dialogId, string $message): array + { + $params = [ + 'BOT_ID' => $botId, + 'DIALOG_ID' => $dialogId, + 'MESSAGE' => $message, + ]; + return self::call($auth, 'imbot.message.add', $params); + } +} diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 0000000..e00f3f2 --- /dev/null +++ b/src/Config.php @@ -0,0 +1,78 @@ + 'Olá! Somos o suporte, já vamos te atender.', + 'default_on_message' => 'Recebemos sua mensagem e responderemos em breve.' + ]; + } + + public static function save(array $config): void + { + if (!is_dir(DATA_DIR)) { + mkdir(DATA_DIR, 0775, true); + } + file_put_contents(self::configPath(), json_encode($config, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); + } + + public static function loadState(): array + { + $path = self::statePath(); + if (is_file($path)) { + $json = file_get_contents($path); + $data = json_decode($json ?: '[]', true); + if (is_array($data)) { + return $data; + } + } + return []; + } + + public static function saveState(array $state): void + { + if (!is_dir(DATA_DIR)) { + mkdir(DATA_DIR, 0775, true); + } + file_put_contents(self::statePath(), json_encode($state, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); + } + + public static function configPath(): string + { + return DATA_DIR . '/config.json'; + } + + public static function statePath(): string + { + return DATA_DIR . '/state.json'; + } + + public static function baseUrlFromGlobals(): string + { + $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') + || (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https') + || (($_SERVER['REQUEST_SCHEME'] ?? '') === 'https'); + $scheme = $isHttps ? 'https' : 'http'; + $host = $_SERVER['HTTP_HOST'] ?? ($_SERVER['SERVER_NAME'] ?? 'localhost'); + $script = $_SERVER['SCRIPT_NAME'] ?? '/'; + $dir = rtrim(str_replace('\\', '/', dirname($script)), '/'); + if ($dir === '' || $dir === '.') { + $dir = '/'; + } + $base = $scheme . '://' . $host . ($dir === '/' ? '' : $dir); + return $base; + } +} diff --git a/src/Logger.php b/src/Logger.php new file mode 100644 index 0000000..315d89a --- /dev/null +++ b/src/Logger.php @@ -0,0 +1,17 @@ +