diff --git a/lib/Command/AddBotParticipant.php b/lib/Command/AddBotParticipant.php
new file mode 100644
index 0000000..f482782
--- /dev/null
+++ b/lib/Command/AddBotParticipant.php
@@ -0,0 +1,123 @@
+config = $config;
+ $this->db = $db;
+ $this->talkManager = $talkManager;
+ $this->userManager = $userManager;
+ $this->attendeeMapper = $attendeeMapper;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $output->writeln('Reading configured rooms from app config...');
+
+ // Get configured rooms from app config (stored as JSON)
+ $roomsJson = $this->config->getAppValue(self::APP_ID, 'rooms', '[]');
+ $rooms = @json_decode($roomsJson, true);
+ if (!is_array($rooms)) {
+ $output->writeln('No configured rooms found.');
+ return 1;
+ }
+
+ $roomTokens = array_keys($rooms);
+ $output->writeln('Found ' . count($roomTokens) . ' configured room(s).');
+
+ // Get talk-bot user
+ $botUser = $this->userManager->get('talk-bot');
+ if ($botUser === null) {
+ $output->writeln('talk-bot user not found. Creating it...');
+ // talk-bot might not exist yet
+ $output->writeln('Run: php occ user:add --password-from-env talk-bot');
+ return 1;
+ }
+
+ // Get table prefix
+ $sysPrefix = $this->config->getSystemValueString('dbtableprefix', '');
+ $talkPrefix = $this->config->getAppValue('spreed', 'databaseprefix', $sysPrefix);
+ $attendeeTable = $talkPrefix . 'talk_attendee';
+
+ $added = 0;
+ $skipped = 0;
+
+ foreach ($roomTokens as $token) {
+ try {
+ $room = $this->talkManager->getRoomForToken($token);
+ $roomId = $room->getId();
+
+ // Check if talk-bot is already an attendee
+ try {
+ $attendee = $this->attendeeMapper->findByActor($roomId, 'users', 'talk-bot');
+ $output->writeln(' Room ' . $token . ': talk-bot already an attendee (id=' . $roomId . ')');
+ $skipped++;
+ continue;
+ } catch (DoesNotExistException $e) {
+ // Not found - will create
+ } catch (\Exception $e) {
+ $output->writeln(' Room ' . $token . ': query error - ' . $e->getMessage() . '');
+ $skipped++;
+ continue;
+ }
+
+ // Create attendee record
+ $newAttendee = new Attendee();
+ $newAttendee->setRoomId($roomId);
+ $newAttendee->setActorType('users');
+ $newAttendee->setActorId('talk-bot');
+ $newAttendee->setDisplayName('talk-bot');
+ $newAttendee->setParticipantType(Participant::PERMISSIONS_DEFAULT);
+ $newAttendee->setPermissions(Participant::PERMISSIONS_MAX_DEFAULT);
+ $newAttendee->setNotificationLevel(3); // Full level
+ $newAttendee->setFavorite(false);
+ $newAttendee->setArchived(false);
+
+ try {
+ $this->attendeeMapper->insert($newAttendee);
+ $output->writeln(' Room ' . $token . ': added talk-bot as attendee (room_id=' . $roomId . ')');
+ $added++;
+ } catch (Exception $e) {
+ $output->writeln(' Room ' . $token . ': insert failed - ' . $e->getMessage() . '');
+ }
+ } catch (\Exception $e) {
+ $output->writeln(' Room ' . $token . ': ' . $e->getMessage() . '');
+ }
+ }
+
+ $output->writeln('');
+ $output->writeln('Done: ' . $added . ' added, ' . $skipped . ' skipped.');
+ return 0;
+ }
+}
diff --git a/lib/Controller/WebhookController.php b/lib/Controller/WebhookController.php
index b9a07b2..da09967 100644
--- a/lib/Controller/WebhookController.php
+++ b/lib/Controller/WebhookController.php
@@ -13,9 +13,11 @@ use OCP\AppFramework\Http\Attribute\AdminRequired;
use OCP\App\IAppManager;
use OCP\IAppConfig;
use OCP\IConfig;
+use OCP\Http\Client\IClientService;
use OCP\IGroupManager;
use OCP\IRequest;
use OCP\IUserSession;
+use OCA\Talk\ParticipantService;
use Psr\Log\LoggerInterface;
class WebhookController extends Controller {
@@ -26,8 +28,9 @@ class WebhookController extends Controller {
private IGroupManager $groupManager;
private IConfig $config;
private IAppConfig $appConfig;
+ private IClientService $clientService;
- public function __construct(IRequest $request, TalkService $talkService, LoggerInterface $logger, IAppManager $appManager, IUserSession $userSession, IGroupManager $groupManager, IConfig $config, IAppConfig $appConfig) {
+ public function __construct(IRequest $request, TalkService $talkService, LoggerInterface $logger, IAppManager $appManager, IUserSession $userSession, IGroupManager $groupManager, IConfig $config, IAppConfig $appConfig, IClientService $clientService) {
parent::__construct('ncdiscordhook', $request);
$this->talkService = $talkService;
$this->logger = $logger;
@@ -36,6 +39,7 @@ class WebhookController extends Controller {
$this->groupManager = $groupManager;
$this->config = $config;
$this->appConfig = $appConfig;
+ $this->clientService = $clientService;
}
/**
@@ -201,6 +205,9 @@ class WebhookController extends Controller {
* Debug endpoint to verify the app is working.
*
* URL: GET /apps/ncdiscordhook/debug
+ * Query params:
+ * - webhook_url: Full webhook URL to diagnose (e.g. /apps/ncdiscordhook/bot-webhook/{room}/{token})
+ * - test_post=1: Actually POST a test message to the room
*/
#[PublicPage]
#[NoCSRFRequired]
@@ -323,9 +330,338 @@ class WebhookController extends Controller {
$info['talk_debug_error_trace'] = substr($e->getTraceAsString(), 0, 2000);
}
+ // ── Webhook URL debug ─────────────────────────────────────────
+ // Parse webhook URL if provided via query param
+ $webhookUrl = $this->request->getParam('webhook_url', '');
+ $doTestPost = (bool) $this->request->getParam('test_post', 0);
+ if ($webhookUrl !== '') {
+ $info['webhook_debug'] = $this->debugWebhookUrl($webhookUrl);
+ if ($doTestPost) {
+ $info['test_post'] = $this->runTestPost($webhookUrl);
+ }
+ }
+
return new DataResponse($info);
}
+ /**
+ * Debug a webhook URL step by step.
+ */
+ private function debugWebhookUrl(string $webhookUrl): array {
+ $result = [];
+
+ // 1. Parse the URL
+ $parsed = parse_url($webhookUrl);
+ $result['parsed_url'] = $parsed;
+
+ $path = $parsed['path'] ?? '';
+ $segments = explode('/', trim($path, '/'));
+
+ // Expected: apps/ncdiscordhook/bot-webhook/{roomToken}/{token}
+ if (count($segments) >= 5 && $segments[0] === 'apps' && $segments[1] === 'ncdiscordhook' && $segments[2] === 'bot-webhook') {
+ $roomToken = $segments[3];
+ $token = $segments[4];
+ $result['room_token'] = $roomToken;
+ $result['token'] = $token;
+ $result['url_valid'] = true;
+
+ // 2. Validate auth token
+ $valid = $this->talkService->validateAuthToken($roomToken, $token);
+ $result['auth_token_valid'] = $valid;
+
+ // 3. Check if room is configured
+ $configured = $this->talkService->isBotEnabledForRoom($roomToken);
+ $result['room_configured'] = $configured;
+
+ // 4. Check bot user
+ try {
+ $bot = $this->talkService->getBotUser();
+ $result['bot_user_exists'] = $bot !== null;
+ $result['bot_uid'] = $bot !== null ? $bot->getUID() : null;
+ } catch (\Exception $e) {
+ $result['bot_user_error'] = $e->getMessage();
+ }
+
+ // 5. Check bot password
+ $result['has_bot_password'] = $this->talkService->hasBotPassword();
+ try {
+ $pw = $this->talkService->getBotPassword();
+ $result['bot_password_length'] = $pw !== null ? strlen($pw) : null;
+ $result['bot_password_starts_with'] = $pw !== null ? substr($pw, 0, 4) . '...' : null;
+ } catch (\Exception $e) {
+ $result['bot_password_error'] = $e->getMessage();
+ }
+
+ // 6. Check base URL
+ $baseUrl = $this->talkService->getBaseUrl();
+ $result['base_url'] = $baseUrl;
+ $result['base_url_scheme'] = parse_url($baseUrl, PHP_URL_SCHEME) ?? null;
+ $result['base_url_host'] = parse_url($baseUrl, PHP_URL_HOST) ?? null;
+
+ // 7. Check if room exists in DB
+ try {
+ $sysPrefix = $this->config->getSystemValueString('dbtableprefix', '');
+ $talkPrefix = $this->config->getAppValue('spreed', 'databaseprefix', $sysPrefix);
+ $roomTable = $this->talkService->detectTalkTableFromCatalog('talk_rooms', 'spreed_room');
+ $result['room_table'] = $roomTable;
+
+ if ($roomTable !== null) {
+ $stmt = $this->talkService->getDbConnection()->executeQuery(
+ 'SELECT token, type, name, object_type, object_id
+ FROM "' . $roomTable . '" WHERE token = ?',
+ [$roomToken],
+ );
+ $row = $stmt->fetch();
+ $stmt->closeCursor();
+
+ $result['room_found_in_db'] = $row !== false;
+ if ($row !== false) {
+ $result['room_type'] = (int)$row['type'];
+ // readable_name may not exist in all Talk versions
+ $result['room_readable_name'] = isset($row['readable_name']) ? ($row['readable_name'] ?: null) : null;
+ $result['room_name'] = $row['name'] ?: null;
+ $result['room_object_type'] = $row['object_type'] ?: null;
+ $result['room_object_id'] = $row['object_id'] ?: null;
+
+ // 8. Check room name doesn't look like a DM JSON
+ if (is_string($row['name']) && str_starts_with($row['name'], '["')) {
+ $result['room_name_is_json_dm'] = true;
+ $result['room_name_is_json_dm_warning'] = 'Room name looks like a DM JSON array — this room type is normally filtered out but is explicitly configured.';
+ }
+ }
+ }
+ } catch (\Exception $e) {
+ $result['db_check_error'] = $e->getMessage();
+ }
+
+ // 9. Construct Chat API endpoint
+ $chatEndpoint = $baseUrl . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $roomToken;
+ $result['chat_api_endpoint'] = $chatEndpoint;
+
+ // 10. Construct Basic auth header (without exposing full password)
+ try {
+ $pw = $this->talkService->getBotPassword();
+ if ($pw !== null) {
+ $b64 = base64_encode('talk-bot:' . $pw);
+ $result['basic_auth_header_prefix'] = 'Basic ' . substr($b64, 0, 20) . '...';
+ $result['basic_auth_header_length'] = strlen($b64);
+ }
+ } catch (\Exception $e) {
+ $result['basic_auth_error'] = $e->getMessage();
+ }
+
+ // 11. Test HTTP connectivity to the Chat API endpoint
+ try {
+ $client = $this->clientService->newClient();
+ $response = $client->get($chatEndpoint, [
+ 'headers' => [
+ 'OCS-Expect-Formatted' => 'json',
+ 'Authorization' => 'Basic ' . base64_encode('talk-bot:' . ($this->talkService->getBotPassword() ?? 'ERROR')),
+ ],
+ 'nextcloud' => [
+ 'allow_local_address' => true,
+ ],
+ ]);
+ $result['http_test_status'] = $response->getStatusCode();
+ $result['http_test_body'] = $response->getBody();
+ } catch (\Guzzle\Exception\ConnectException $e) {
+ $result['http_test_error'] = 'Connection failed: ' . $e->getMessage();
+ $result['http_test_error_type'] = 'connection';
+ } catch (\Guzzle\Exception\ServerException $e) {
+ $result['http_test_status'] = $e->getResponse() ? $e->getResponse()->getStatusCode() : null;
+ $result['http_test_body'] = $e->getResponse() ? $e->getResponse()->getBody() : null;
+ $result['http_test_error'] = 'Server error: ' . $e->getMessage();
+ $result['http_test_error_type'] = 'server';
+ } catch (\Guzzle\Exception\ClientException $e) {
+ $result['http_test_status'] = $e->getResponse() ? $e->getResponse()->getStatusCode() : null;
+ $result['http_test_body'] = $e->getResponse() ? $e->getResponse()->getBody() : null;
+ $result['http_test_error'] = 'Client error: ' . $e->getMessage();
+ $result['http_test_error_type'] = 'client';
+ } catch (\Exception $e) {
+ $result['http_test_error'] = $e->getMessage();
+ $result['http_test_error_type'] = 'other';
+ }
+
+ // 12. Summary of issues
+ $issues = [];
+ if (!$result['auth_token_valid'] ?? false === false) {
+ $issues[] = 'Auth token is INVALID for this room';
+ }
+ if (!($result['room_configured'] ?? false)) {
+ $issues[] = 'Room is NOT configured (bot not enabled)';
+ }
+ if (!($result['bot_user_exists'] ?? false)) {
+ $issues[] = 'talk-bot user does NOT exist';
+ }
+ if (!($result['has_bot_password'] ?? false)) {
+ $issues[] = 'Bot password is NOT configured';
+ }
+ if (($result['room_found_in_db'] ?? false) === false) {
+ $issues[] = 'Room NOT found in Talk database';
+ }
+ if (($result['http_test_status'] ?? null) !== null && ($result['http_test_status'] ?? 0) >= 400) {
+ $issues[] = 'Chat API returned HTTP ' . ($result['http_test_status'] ?? 'unknown');
+ }
+ if (isset($result['http_test_error'])) {
+ $issues[] = 'HTTP test failed: ' . $result['http_test_error'];
+ }
+ $result['issues'] = $issues;
+ $result['summary'] = empty($issues) ? 'All checks passed — if webhook still fails, check Talk server logs' : implode('; ', $issues);
+
+ } else {
+ $result['url_valid'] = false;
+ $result['parse_error'] = 'URL does not match expected pattern: /apps/ncdiscordhook/bot-webhook/{roomToken}/{token}';
+ }
+
+ return $result;
+ }
+
+ /**
+ * Run a test post to simulate a full webhook delivery.
+ */
+ private function runTestPost(string $webhookUrl): array {
+ $result = [];
+
+ // Parse URL to get roomToken and token
+ $parsed = parse_url($webhookUrl);
+ $path = $parsed['path'] ?? '';
+ $segments = explode('/', trim($path, '/'));
+
+ if (!(count($segments) >= 5 && $segments[0] === 'apps' && $segments[1] === 'ncdiscordhook' && $segments[2] === 'bot-webhook')) {
+ return ['error' => 'Invalid webhook URL format'];
+ }
+
+ $roomToken = $segments[3];
+ $token = $segments[4];
+ $result['room_token'] = $roomToken;
+ $result['token'] = $token;
+
+ // Step 1: Validate auth token
+ $authValid = $this->talkService->validateAuthToken($roomToken, $token);
+ $result['step_1_auth_token'] = $authValid ? 'OK' : 'FAIL — token not found for this room';
+ if (!$authValid) {
+ $result['steps'] = $result;
+ return $result;
+ }
+
+ // Step 2: Build a realistic Discord-style test payload
+ $testPayload = [
+ 'content' => '🔧 **Test message from ncdiscordhook debug**\n\nThis is a test message sent via the debug endpoint. If you see this, the webhook pipeline is working!',
+ 'embeds' => [
+ [
+ 'title' => 'Webhook Test',
+ 'description' => 'Sent at ' . date('Y-m-d H:i:s T'),
+ 'color' => 3066993,
+ 'footer' => [
+ 'text' => 'ncdiscordhook debug endpoint',
+ ],
+ ],
+ ],
+ 'username' => 'Debug Test',
+ 'avatar_url' => null,
+ ];
+
+ // Step 3: Map payload to message
+ $message = $this->talkService->mapPayload($testPayload);
+ $result['step_2_map_payload'] = $message !== '' ? 'OK — "' . substr($message, 0, 100) . '..." (' . strlen($message) . ' chars)' : 'FAIL — empty message';
+ if ($message === '') {
+ return $result;
+ }
+
+ // Step 4: Get sender name
+ $senderName = $this->talkService->getSenderName($testPayload);
+ $result['step_3_sender_name'] = $senderName;
+
+ // Step 5: Check bot password
+ $botPassword = $this->talkService->getBotPassword();
+ $result['step_4_bot_password'] = $botPassword !== null ? 'OK (' . strlen($botPassword) . ' chars)' : 'FAIL — not configured';
+ if ($botPassword === null) {
+ return $result;
+ }
+
+ // Step 6: Check bot enabled for room
+ $botEnabled = $this->talkService->isBotEnabledForRoom($roomToken);
+ $result['step_5_bot_enabled'] = $botEnabled ? 'OK' : 'FAIL — room not enabled in config';
+ if (!$botEnabled) {
+ return $result;
+ }
+
+ // Step 7: Get base URL
+ $baseUrl = $this->talkService->getBaseUrl();
+ $result['step_6_base_url'] = $baseUrl !== '' ? $baseUrl : 'FAIL — not configured';
+ if ($baseUrl === '') {
+ return $result;
+ }
+
+ // Step 8: Build the Chat API request (OCS path with CSRF bypass)
+ $endpoint = $baseUrl . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $roomToken;
+ $result['step_7_endpoint'] = $endpoint;
+
+ // Step 9: Try the actual Chat API POST
+ try {
+ $client = $this->clientService->newClient();
+ $response = $client->post($endpoint, [
+ 'body' => json_encode([
+ 'message' => $message,
+ 'actorType' => 'users',
+ 'actorId' => 'talk-bot',
+ 'actorDisplayName' => $senderName,
+ ]),
+ 'headers' => [
+ 'OCS-Expect-Formatted' => 'json',
+ 'OCS-APIRequest' => '1',
+ 'Authorization' => 'Basic ' . base64_encode('talk-bot:' . $botPassword),
+ 'Content-Type' => 'application/json',
+ ],
+ 'nextcloud' => [
+ 'allow_local_address' => true,
+ ],
+ ]);
+
+ $statusCode = $response->getStatusCode();
+ $body = $response->getBody();
+ $result['step_8_http_status'] = $statusCode;
+ $result['step_8_http_body'] = $body;
+
+ if ($statusCode === 200) {
+ $result['step_8_result'] = 'SUCCESS — message posted to Talk!';
+ $result['overall'] = 'Webhook pipeline is WORKING. If messages still fail from Discord, check your Discord webhook configuration.';
+ } else {
+ $result['step_8_result'] = 'FAIL — HTTP ' . $statusCode;
+
+ // Parse OCS error if possible
+ if (strpos($body, 'ocs') !== false) {
+ $parsedBody = @json_decode($body, true);
+ if (is_array($parsedBody) && isset($parsedBody['ocs']['meta']['message'])) {
+ $result['step_8_error_message'] = $parsedBody['ocs']['meta']['message'];
+ }
+ }
+ }
+ } catch (\Guzzle\Exception\ConnectException $e) {
+ $result['step_8_result'] = 'FAIL — Connection error';
+ $result['step_8_error'] = $e->getMessage();
+ } catch (\Guzzle\Exception\ServerException $e) {
+ $result['step_8_result'] = 'FAIL — Server error (5xx)';
+ $result['step_8_error'] = $e->getMessage();
+ if ($e->getResponse()) {
+ $result['step_8_status'] = $e->getResponse()->getStatusCode();
+ $result['step_8_body'] = $e->getResponse()->getBody();
+ }
+ } catch (\Guzzle\Exception\ClientException $e) {
+ $result['step_8_result'] = 'FAIL — Client error (4xx)';
+ $result['step_8_error'] = $e->getMessage();
+ if ($e->getResponse()) {
+ $result['step_8_status'] = $e->getResponse()->getStatusCode();
+ $result['step_8_body'] = $e->getResponse()->getBody();
+ }
+ } catch (\Exception $e) {
+ $result['step_8_result'] = 'FAIL — Unexpected error';
+ $result['step_8_error'] = $e->getMessage();
+ }
+
+ return $result;
+ }
+
/**
* Get available Talk rooms.
*
diff --git a/lib/Service/TalkService.php b/lib/Service/TalkService.php
index 714868e..8421b7e 100644
--- a/lib/Service/TalkService.php
+++ b/lib/Service/TalkService.php
@@ -116,16 +116,41 @@ class TalkService {
return rtrim($overwritten, '/');
}
- // Use the first trusted domain as the base URL.
- // This is the only reliable method when the HTTP client blocks localhost
- // and overwrite.cli.url points to an internal Docker address.
+ // Prefer a public (non-loopback, non-private) trusted domain.
+ // In Docker/container deployments, trusted_domains[0] is often 127.0.0.1
+ // which is unreachable from within the container.
$trusted = $this->config->getSystemValue('trusted_domains', []);
+ foreach ($trusted as $domain) {
+ if ($domain === '') {
+ continue;
+ }
+ // Already has a scheme — trust it as-is
+ if (preg_match('#^https?://#', $domain)) {
+ return rtrim($domain, '/');
+ }
+ // Skip loopback and localhost
+ $lower = strtolower($domain);
+ if ($lower === 'localhost'
+ || $lower === '127.0.0.1'
+ || $lower === '::1'
+ ) {
+ continue;
+ }
+ // Skip private IP ranges (10.x, 172.16-31.x, 192.168.x)
+ if (preg_match(
+ '/^(10\.\d{1,3}|172\.(1[6-9]|2\d|3[01])|192\.168)\./i',
+ $domain
+ )) {
+ continue;
+ }
+ return 'https://' . $domain;
+ }
+
+ // Fallback to first trusted domain (may be localhost; caller may fail)
if (!empty($trusted[0])) {
- // Check if trusted_domains[0] already has a scheme
if (preg_match('#^https?://#', $trusted[0])) {
return rtrim($trusted[0], '/');
}
- // Use https by default for trusted domains
return 'https://' . $trusted[0];
}
@@ -706,6 +731,7 @@ class TalkService {
'body' => $jsonBody,
'headers' => [
'OCS-Expect-Formatted' => 'json',
+ 'OCS-APIRequest' => '1',
'Authorization' => 'Basic ' . $credentials,
'Content-Type' => 'application/json',
],
@@ -715,7 +741,7 @@ class TalkService {
]);
$statusCode = $response->getStatusCode();
- if ($statusCode === Http::STATUS_OK) {
+ if ($statusCode >= 200 && $statusCode < 300) {
$this->logger->info('NCdiscordhook: message posted to room ' . $roomToken, [
'app' => self::APP_ID,
]);
@@ -838,5 +864,68 @@ class TalkService {
if (isset($config['sender_name'])) {
$this->setSenderName($config['sender_name']);
}
+
+ // Ensure talk-bot is a participant in all configured rooms
+ $this->ensureBotParticipants();
+ }
+
+ /**
+ * Add talk-bot user as a participant in all configured rooms.
+ * Required so the Chat API accepts requests authenticated as talk-bot.
+ */
+ private function ensureBotParticipants(): void {
+ if ($this->talkParticipantService === null) {
+ $this->logger->info('NCdiscordhook: ParticipantService not available, skipping auto-join for talk-bot', ['app' => self::APP_ID]);
+ return;
+ }
+
+ $rooms = $this->getRooms();
+ if (empty($rooms)) {
+ return;
+ }
+
+ // Get the talk-bot user
+ $botUser = $this->userManager->get('talk-bot');
+ if ($botUser === null) {
+ $this->logger->warning('NCdiscordhook: talk-bot user not found', ['app' => self::APP_ID]);
+ return;
+ }
+
+ foreach (array_keys($rooms) as $token) {
+ try {
+ $room = $this->talkManager->getRoomForToken($token);
+ // Check if talk-bot is already a participant
+ $participants = $room->getParticipants();
+ $actors = [];
+ if (is_array($participants)) {
+ $actors = $participants['actors'] ?? [];
+ } elseif (is_object($participants) && method_exists($participants, 'getActors')) {
+ $actors = $participants->getActors();
+ }
+ $hasBot = false;
+ foreach ($actors as $actor) {
+ if (isset($actor['type']) && $actor['type'] === 'users' && isset($actor['actorId']) && $actor['actorId'] === 'talk-bot') {
+ $hasBot = true;
+ break;
+ }
+ }
+ if (!$hasBot) {
+ $this->talkParticipantService->addParticipant(
+ $room,
+ $botUser,
+ \OCA\Talk\Participant::TYPE_USER,
+ \OCA\Talk\Participant::PERMISSIONS_DEFAULT,
+ );
+ $this->logger->info('NCdiscordhook: added talk-bot as participant in room ' . $token, [
+ 'app' => self::APP_ID,
+ ]);
+ }
+ } catch (\Exception $e) {
+ $this->logger->warning('NCdiscordhook: failed to add talk-bot to room ' . $token, [
+ 'app' => self::APP_ID,
+ 'error' => $e->getMessage(),
+ ]);
+ }
+ }
}
}