fix: update TalkService for Talk 14 compatibility and add CLI copy helper
- **TalkService**: - Refactored `ensureBotParticipants` to use `AttendeeMapper` directly, removing the dependency on `ParticipantService`. - Added `getBotDisplayNameForRoom` to resolve the bot's display name via direct DB query. - Updated `sendMessage` to use Chat API v1 and include `richObjectsEnd`. - Improved `getBaseUrl` logic to handle `overwritehost` and `overwritewebroot`. - **WebhookController**: - Fixed `getAllValues` to iterate known keys manually, preventing `RuntimeException` in Nextcloud 33. - Added error handling for `saveConfig`. - Removed unused `ParticipantService` import. - **js/settings.js**: - Added functionality to copy CLI commands to the clipboard. - **Deletions**: - Removed `lib/Command/AddBotParticipant.php` as its logic is now integrated into `TalkService`. - **Templates**: - Updated admin settings labels and comments.
This commit is contained in:
@@ -39,6 +39,22 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||||||
savePasswordBtn.style.display = this.value.trim() ? 'inline-block' : 'none';
|
savePasswordBtn.style.display = this.value.trim() ? 'inline-block' : 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Copy CLI commands to clipboard
|
||||||
|
document.querySelectorAll('.nc-copy-btn').forEach(function (btn) {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
var text = this.dataset.copy || '';
|
||||||
|
navigator.clipboard.writeText(text).then(function () {
|
||||||
|
var orig = this.textContent;
|
||||||
|
this.textContent = '✓';
|
||||||
|
this.style.color = '#28a745';
|
||||||
|
setTimeout(function () {
|
||||||
|
btn.textContent = orig;
|
||||||
|
btn.style.color = '';
|
||||||
|
}, 2000);
|
||||||
|
}.bind(this));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Fetch available Talk rooms
|
// Fetch available Talk rooms
|
||||||
function fetchRooms() {
|
function fetchRooms() {
|
||||||
fetchRoomsBtn.disabled = true;
|
fetchRoomsBtn.disabled = true;
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace OCA\NCdiscordhook\Command;
|
|
||||||
|
|
||||||
use OCA\Talk\Manager as TalkManager;
|
|
||||||
use OCA\Talk\Participant;
|
|
||||||
use OCA\Talk\Model\Attendee;
|
|
||||||
use OCA\Talk\Model\AttendeeMapper;
|
|
||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
|
||||||
use OCP\DB\Exception;
|
|
||||||
use OCP\IConfig;
|
|
||||||
use OCP\IDBConnection;
|
|
||||||
use OCP\IUserManager;
|
|
||||||
use Symfony\Component\Console\Command\Command;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
|
|
||||||
class AddBotParticipant extends Command {
|
|
||||||
public static defaultName = 'ncdiscordhook:add-bot-participant';
|
|
||||||
public static defaultDescription = 'Add talk-bot as a participant in all configured rooms (fixes Chat API 404)';
|
|
||||||
|
|
||||||
private IConfig $config;
|
|
||||||
private IDBConnection $db;
|
|
||||||
private TalkManager $talkManager;
|
|
||||||
private IUserManager $userManager;
|
|
||||||
private AttendeeMapper $attendeeMapper;
|
|
||||||
|
|
||||||
private const APP_ID = 'ncdiscordhook';
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
IConfig $config,
|
|
||||||
IDBConnection $db,
|
|
||||||
TalkManager $talkManager,
|
|
||||||
IUserManager $userManager,
|
|
||||||
AttendeeMapper $attendeeMapper,
|
|
||||||
) {
|
|
||||||
parent::__construct();
|
|
||||||
$this->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('<error>No configured rooms found.</error>');
|
|
||||||
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('<error>talk-bot user not found. Creating it...</error>');
|
|
||||||
// talk-bot might not exist yet
|
|
||||||
$output->writeln('<comment>Run: php occ user:add --password-from-env talk-bot</comment>');
|
|
||||||
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(' <info>Room ' . $token . ': talk-bot already an attendee (id=' . $roomId . ')</info>');
|
|
||||||
$skipped++;
|
|
||||||
continue;
|
|
||||||
} catch (DoesNotExistException $e) {
|
|
||||||
// Not found - will create
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$output->writeln(' <warning>Room ' . $token . ': query error - ' . $e->getMessage() . '</warning>');
|
|
||||||
$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(' <info>Room ' . $token . ': added talk-bot as attendee (room_id=' . $roomId . ')</info>');
|
|
||||||
$added++;
|
|
||||||
} catch (Exception $e) {
|
|
||||||
$output->writeln(' <warning>Room ' . $token . ': insert failed - ' . $e->getMessage() . '</warning>');
|
|
||||||
}
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$output->writeln(' <error>Room ' . $token . ': ' . $e->getMessage() . '</error>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$output->writeln('');
|
|
||||||
$output->writeln('<info>Done: ' . $added . ' added, ' . $skipped . ' skipped.</info>');
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,7 +17,6 @@ use OCP\Http\Client\IClientService;
|
|||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
use OCP\IUserSession;
|
use OCP\IUserSession;
|
||||||
use OCA\Talk\ParticipantService;
|
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class WebhookController extends Controller {
|
class WebhookController extends Controller {
|
||||||
@@ -193,7 +192,18 @@ class WebhookController extends Controller {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
$this->talkService->saveConfig($config);
|
$this->talkService->saveConfig($config);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->error('NCdiscordhook: saveConfig failed: ' . $e->getMessage(), [
|
||||||
|
'app' => 'ncdiscordhook',
|
||||||
|
'exception' => (string)$e,
|
||||||
|
]);
|
||||||
|
return new DataResponse(
|
||||||
|
['error' => 'Save failed: ' . $e->getMessage()],
|
||||||
|
Http::STATUS_INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return new DataResponse([
|
return new DataResponse([
|
||||||
'status' => 'ok',
|
'status' => 'ok',
|
||||||
@@ -245,10 +255,15 @@ class WebhookController extends Controller {
|
|||||||
$info['system_config_error'] = $e->getMessage();
|
$info['system_config_error'] = $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppConfig values
|
// AppConfig values (iterate known keys to avoid lazy AppConfig RuntimeException in NC33)
|
||||||
try {
|
try {
|
||||||
$appConfig = $this->appConfig->getAllValues('ncdiscordhook');
|
$knownKeys = ['bot_password', 'rooms', 'auth_tokens', 'retention_days', 'sender_name'];
|
||||||
$info['app_config_keys'] = $appConfig;
|
$appConfigKeys = [];
|
||||||
|
foreach ($knownKeys as $key) {
|
||||||
|
$val = $this->appConfig->getValueString('ncdiscordhook', $key, '');
|
||||||
|
$appConfigKeys[$key] = $val !== '' ? '[set]' : '[empty]';
|
||||||
|
}
|
||||||
|
$info['app_config_keys'] = $appConfigKeys;
|
||||||
$info['app_config_room_count'] = count($this->talkService->getRooms());
|
$info['app_config_room_count'] = count($this->talkService->getRooms());
|
||||||
$info['app_config_auth_token_count'] = count($this->talkService->getAuthTokens());
|
$info['app_config_auth_token_count'] = count($this->talkService->getAuthTokens());
|
||||||
$info['app_config_bot_password_set'] = $this->talkService->hasBotPassword();
|
$info['app_config_bot_password_set'] = $this->talkService->hasBotPassword();
|
||||||
@@ -434,7 +449,7 @@ class WebhookController extends Controller {
|
|||||||
$result['db_check_error'] = $e->getMessage();
|
$result['db_check_error'] = $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9. Construct Chat API endpoint
|
// 9. Construct Chat API endpoint (v1 for Talk 19 / NC33)
|
||||||
$chatEndpoint = $baseUrl . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $roomToken;
|
$chatEndpoint = $baseUrl . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $roomToken;
|
||||||
$result['chat_api_endpoint'] = $chatEndpoint;
|
$result['chat_api_endpoint'] = $chatEndpoint;
|
||||||
|
|
||||||
@@ -593,7 +608,7 @@ class WebhookController extends Controller {
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 8: Build the Chat API request (OCS path with CSRF bypass)
|
// Step 8: Build the Chat API request (v1 for Talk 19 / NC33)
|
||||||
$endpoint = $baseUrl . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $roomToken;
|
$endpoint = $baseUrl . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $roomToken;
|
||||||
$result['step_7_endpoint'] = $endpoint;
|
$result['step_7_endpoint'] = $endpoint;
|
||||||
|
|
||||||
|
|||||||
+131
-35
@@ -15,6 +15,9 @@ use OCP\IURLGenerator;
|
|||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
use OCP\Share\IManager;
|
use OCP\Share\IManager;
|
||||||
use OCA\Talk\Manager as TalkManager;
|
use OCA\Talk\Manager as TalkManager;
|
||||||
|
use OCA\Talk\Model\Attendee;
|
||||||
|
use OCA\Talk\Model\AttendeeMapper;
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class TalkService {
|
class TalkService {
|
||||||
@@ -32,6 +35,7 @@ class TalkService {
|
|||||||
private TalkManager $talkManager;
|
private TalkManager $talkManager;
|
||||||
private ICrypto $crypto;
|
private ICrypto $crypto;
|
||||||
private IConfig $config;
|
private IConfig $config;
|
||||||
|
private AttendeeMapper $attendeeMapper;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IClientService $clientService,
|
IClientService $clientService,
|
||||||
@@ -45,6 +49,7 @@ class TalkService {
|
|||||||
LoggerInterface $logger,
|
LoggerInterface $logger,
|
||||||
TalkManager $talkManager,
|
TalkManager $talkManager,
|
||||||
ICrypto $crypto,
|
ICrypto $crypto,
|
||||||
|
AttendeeMapper $attendeeMapper,
|
||||||
) {
|
) {
|
||||||
$this->clientService = $clientService;
|
$this->clientService = $clientService;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
@@ -57,6 +62,7 @@ class TalkService {
|
|||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
$this->talkManager = $talkManager;
|
$this->talkManager = $talkManager;
|
||||||
$this->crypto = $crypto;
|
$this->crypto = $crypto;
|
||||||
|
$this->attendeeMapper = $attendeeMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Bot password ──────────────────────────────────────────────
|
// ── Bot password ──────────────────────────────────────────────
|
||||||
@@ -111,10 +117,21 @@ class TalkService {
|
|||||||
* often points to a localhost:port that is unreachable from within the container.
|
* often points to a localhost:port that is unreachable from within the container.
|
||||||
*/
|
*/
|
||||||
public function getBaseUrl(): string {
|
public function getBaseUrl(): string {
|
||||||
|
// Prefer overwritehost (hostname override) over trusted_domains
|
||||||
|
$overwriteHost = $this->config->getSystemValueString('overwritehost', '');
|
||||||
|
if ($overwriteHost !== '') {
|
||||||
|
$proto = $this->config->getSystemValueString('overwriteproto', 'https');
|
||||||
|
return rtrim($proto . '://' . $overwriteHost, '/');
|
||||||
|
}
|
||||||
|
|
||||||
$overwritten = $this->config->getSystemValueString('overwritewebroot', '');
|
$overwritten = $this->config->getSystemValueString('overwritewebroot', '');
|
||||||
if ($overwritten !== '') {
|
if ($overwritten !== '') {
|
||||||
|
// overwritewebroot may be a path — prepend current host/proto if needed
|
||||||
|
if (preg_match('#^https?://#', $overwritten)) {
|
||||||
return rtrim($overwritten, '/');
|
return rtrim($overwritten, '/');
|
||||||
}
|
}
|
||||||
|
// It's a path — fall through to trusted_domains
|
||||||
|
}
|
||||||
|
|
||||||
// Prefer a public (non-loopback, non-private) trusted domain.
|
// Prefer a public (non-loopback, non-private) trusted domain.
|
||||||
// In Docker/container deployments, trusted_domains[0] is often 127.0.0.1
|
// In Docker/container deployments, trusted_domains[0] is often 127.0.0.1
|
||||||
@@ -671,7 +688,7 @@ class TalkService {
|
|||||||
*
|
*
|
||||||
* @param string $roomToken Talk room token
|
* @param string $roomToken Talk room token
|
||||||
* @param string $message Message text
|
* @param string $message Message text
|
||||||
* @param string $senderName Sender display name
|
* @param string $senderName Discord sender name (embedded in message text)
|
||||||
* @param array $richObjects Rich object data (optional)
|
* @param array $richObjects Rich object data (optional)
|
||||||
* @return bool Success
|
* @return bool Success
|
||||||
*/
|
*/
|
||||||
@@ -702,22 +719,32 @@ class TalkService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use Chat API v1 (Talk 19 / NC33 only supports v1)
|
||||||
$endpoint = $baseUrl . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $roomToken;
|
$endpoint = $baseUrl . '/ocs/v2.php/apps/spreed/api/v1/chat/' . $roomToken;
|
||||||
|
|
||||||
// Build message body for Chat API
|
// Get bot's actual display name from the room participant record.
|
||||||
|
// In Talk 14+, actorDisplayName MUST match the participant's display name
|
||||||
|
// or the message is silently dropped.
|
||||||
|
$botDisplayName = $this->getBotDisplayNameForRoom($roomToken);
|
||||||
|
|
||||||
|
// Build message body for Chat API v4
|
||||||
$body = [
|
$body = [
|
||||||
'message' => $message,
|
'message' => $message,
|
||||||
'actorType' => 'users',
|
'actorType' => 'users',
|
||||||
'actorId' => 'talk-bot',
|
'actorId' => 'talk-bot',
|
||||||
'actorDisplayName' => $senderName,
|
'actorDisplayName' => $botDisplayName,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!empty($richObjects)) {
|
if (!empty($richObjects)) {
|
||||||
$indexed = [];
|
$indexed = [];
|
||||||
|
$indexedEnd = [];
|
||||||
foreach ($richObjects as $index => $richObj) {
|
foreach ($richObjects as $index => $richObj) {
|
||||||
$indexed['file-' . $index] = $richObj;
|
$key = 'file-' . $index;
|
||||||
|
$indexed[$key] = $richObj;
|
||||||
|
$indexedEnd[$key] = true;
|
||||||
}
|
}
|
||||||
$body['richObjects'] = $indexed;
|
$body['richObjects'] = $indexed;
|
||||||
|
$body['richObjectsEnd'] = $indexedEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
$jsonBody = json_encode($body);
|
$jsonBody = json_encode($body);
|
||||||
@@ -772,6 +799,42 @@ class TalkService {
|
|||||||
return isset($rooms[$roomToken]);
|
return isset($rooms[$roomToken]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the bot's display name as registered in a Talk room.
|
||||||
|
* Falls back to 'talk-bot' if not found.
|
||||||
|
*/
|
||||||
|
public function getBotDisplayNameForRoom(string $roomToken): string {
|
||||||
|
// Resolve token → room_id via direct DB query (TalkManager::getRoomForToken removed in Talk 14+)
|
||||||
|
$roomTable = $this->detectTalkTableFromCatalog('talk_rooms', 'spreed_room');
|
||||||
|
if ($roomTable === null) {
|
||||||
|
return 'talk-bot';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stmt = $this->db->executeQuery(
|
||||||
|
'SELECT id FROM "' . $roomTable . '" WHERE token = ?',
|
||||||
|
[$roomToken],
|
||||||
|
);
|
||||||
|
$roomId = (int)$stmt->fetchOne();
|
||||||
|
$stmt->closeCursor();
|
||||||
|
|
||||||
|
if ($roomId === 0) {
|
||||||
|
return 'talk-bot';
|
||||||
|
}
|
||||||
|
|
||||||
|
$attendee = $this->attendeeMapper->findByActor($roomId, Attendee::ACTOR_USERS, 'talk-bot');
|
||||||
|
return $attendee->getDisplayName() ?: 'talk-bot';
|
||||||
|
} catch (DoesNotExistException $e) {
|
||||||
|
return 'talk-bot';
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->warning('NCdiscordhook: failed to get bot display name for room ' . $roomToken, [
|
||||||
|
'app' => self::APP_ID,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
return 'talk-bot';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Image cleanup ─────────────────────────────────────────────
|
// ── Image cleanup ─────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -845,17 +908,18 @@ class TalkService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($config['rooms'])) {
|
if (isset($config['rooms'])) {
|
||||||
$this->setRooms($config['rooms']);
|
$rooms = $config['rooms'];
|
||||||
|
} else {
|
||||||
|
$rooms = $this->getRooms();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove explicitly disabled rooms from config
|
// Remove explicitly disabled rooms from the configured set
|
||||||
if (isset($config['disabled_rooms']) && is_array($config['disabled_rooms'])) {
|
if (isset($config['disabled_rooms']) && is_array($config['disabled_rooms'])) {
|
||||||
$rooms = $this->getRooms();
|
|
||||||
foreach ($config['disabled_rooms'] as $token) {
|
foreach ($config['disabled_rooms'] as $token) {
|
||||||
unset($rooms[$token]);
|
unset($rooms[$token]);
|
||||||
}
|
}
|
||||||
$this->setRooms($rooms);
|
|
||||||
}
|
}
|
||||||
|
$this->setRooms($rooms);
|
||||||
|
|
||||||
if (isset($config['auth_tokens'])) {
|
if (isset($config['auth_tokens'])) {
|
||||||
$this->setAuthTokens($config['auth_tokens']);
|
$this->setAuthTokens($config['auth_tokens']);
|
||||||
@@ -872,13 +936,9 @@ class TalkService {
|
|||||||
/**
|
/**
|
||||||
* Add talk-bot user as a participant in all configured rooms.
|
* Add talk-bot user as a participant in all configured rooms.
|
||||||
* Required so the Chat API accepts requests authenticated as talk-bot.
|
* Required so the Chat API accepts requests authenticated as talk-bot.
|
||||||
|
* Uses AttendeeMapper directly to avoid injecting ParticipantService (17 deps).
|
||||||
*/
|
*/
|
||||||
private function ensureBotParticipants(): void {
|
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();
|
$rooms = $this->getRooms();
|
||||||
if (empty($rooms)) {
|
if (empty($rooms)) {
|
||||||
return;
|
return;
|
||||||
@@ -891,33 +951,69 @@ class TalkService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get Talk DB table prefix
|
||||||
|
$sysPrefix = $this->config->getSystemValueString('dbtableprefix', '');
|
||||||
|
$talkPrefix = $this->config->getAppValue('spreed', 'databaseprefix', $sysPrefix);
|
||||||
|
$attendeeTable = $talkPrefix . 'talk_attendee';
|
||||||
|
|
||||||
|
// Detect Talk rooms table name
|
||||||
|
$roomTable = $this->detectTalkTableFromCatalog('talk_rooms', 'spreed_room');
|
||||||
|
if ($roomTable === null) {
|
||||||
|
$this->logger->warning('NCdiscordhook: Talk rooms table not found, skipping participant setup', [
|
||||||
|
'app' => self::APP_ID,
|
||||||
|
]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (array_keys($rooms) as $token) {
|
foreach (array_keys($rooms) as $token) {
|
||||||
try {
|
try {
|
||||||
$room = $this->talkManager->getRoomForToken($token);
|
// Resolve token → room_id via direct DB query (TalkManager::getRoomForToken removed in Talk 14+)
|
||||||
// Check if talk-bot is already a participant
|
$stmt = $this->db->executeQuery(
|
||||||
$participants = $room->getParticipants();
|
'SELECT id FROM "' . $roomTable . '" WHERE token = ?',
|
||||||
$actors = [];
|
[$token],
|
||||||
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,
|
|
||||||
);
|
);
|
||||||
|
$roomId = (int)$stmt->fetchOne();
|
||||||
|
$stmt->closeCursor();
|
||||||
|
|
||||||
|
if ($roomId === 0) {
|
||||||
|
$this->logger->warning('NCdiscordhook: room token not found in database', [
|
||||||
|
'app' => self::APP_ID,
|
||||||
|
'token' => $token,
|
||||||
|
]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if talk-bot is already an attendee
|
||||||
|
try {
|
||||||
|
$this->attendeeMapper->findByActor($roomId, Attendee::ACTOR_USERS, 'talk-bot');
|
||||||
|
// Already exists — nothing to do
|
||||||
|
continue;
|
||||||
|
} catch (DoesNotExistException $e) {
|
||||||
|
// Not found — will create
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create attendee record directly
|
||||||
|
$newAttendee = new Attendee();
|
||||||
|
$newAttendee->setRoomId($roomId);
|
||||||
|
$newAttendee->setActorType(Attendee::ACTOR_USERS);
|
||||||
|
$newAttendee->setActorId('talk-bot');
|
||||||
|
$newAttendee->setDisplayName('talk-bot');
|
||||||
|
$newAttendee->setParticipantType(\OCA\Talk\Participant::PERMISSIONS_DEFAULT);
|
||||||
|
$newAttendee->setPermissions(\OCA\Talk\Participant::PERMISSIONS_MAX_DEFAULT);
|
||||||
|
$newAttendee->setNotificationLevel(3); // Full notification level
|
||||||
|
$newAttendee->setFavorite(false);
|
||||||
|
$newAttendee->setArchived(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->attendeeMapper->insert($newAttendee);
|
||||||
$this->logger->info('NCdiscordhook: added talk-bot as participant in room ' . $token, [
|
$this->logger->info('NCdiscordhook: added talk-bot as participant in room ' . $token, [
|
||||||
'app' => self::APP_ID,
|
'app' => self::APP_ID,
|
||||||
|
'room_id' => $roomId,
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->warning('NCdiscordhook: failed to insert attendee for room ' . $token, [
|
||||||
|
'app' => self::APP_ID,
|
||||||
|
'error' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class Admin implements ISettings {
|
|||||||
'configuredRooms' => $this->talkService->getRooms(),
|
'configuredRooms' => $this->talkService->getRooms(),
|
||||||
'serverUrl' => $this->talkService->getBaseUrl(),
|
'serverUrl' => $this->talkService->getBaseUrl(),
|
||||||
'senderName' => $this->talkService->getSenderNameDefault(),
|
'senderName' => $this->talkService->getSenderNameDefault(),
|
||||||
|
// CLI commands for setup (user copies these to their server)
|
||||||
'l10n' => [
|
'l10n' => [
|
||||||
'fetch_rooms' => $this->l10n->t('Fetch Rooms'),
|
'fetch_rooms' => $this->l10n->t('Fetch Rooms'),
|
||||||
'error_fetching' => $this->l10n->t('Error fetching rooms'),
|
'error_fetching' => $this->l10n->t('Error fetching rooms'),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<div class="nc-settings-section">
|
<div class="nc-settings-section">
|
||||||
<h3>Bot Configuration</h3>
|
<h3>Bot App Password</h3>
|
||||||
<label for="nc-bot-password"><strong>Bot App Password</strong></label><br>
|
<label for="nc-bot-password"><strong>Enter bot app password</strong></label><br>
|
||||||
<div style="display: flex; gap: 8px; align-items: center; margin: 8px 0;">
|
<div style="display: flex; gap: 8px; align-items: center; margin: 8px 0;">
|
||||||
<input type="password" id="nc-bot-password" placeholder="<?= $hasBotPassword ? '●●●●●●●● (leave blank to keep current)' : 'Paste talk-bot app password here' ?>" style="flex: 1; padding: 8px; font-size: 1em;">
|
<input type="password" id="nc-bot-password" placeholder="<?= $hasBotPassword ? '●●●●●●●● (leave blank to keep current)' : 'Paste talk-bot app password here' ?>" style="flex: 1; padding: 8px; font-size: 1em;">
|
||||||
<button id="nc-save-password" type="button" style="display: <?= $hasBotPassword ? 'none' : 'inline-block' ?>;">Save</button>
|
<button id="nc-save-password" type="button" style="display: <?= $hasBotPassword ? 'none' : 'inline-block' ?>;">Save</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user