feat: add command to toggle debug mode

- Add lib/Command/DebugToggle.php to enable/disable debug endpoint via CLI
- Update lib/Controller/WebhookController.php to check debug status
- Add support for base64 attachments in lib/Service/TalkService.php
- Bump version in appinfo/info.xml to 1.1.0
- Update INSTALL.md and README.md documentation
This commit is contained in:
kyle
2026-06-12 17:42:36 -07:00
parent 88ad358eef
commit 28d0187760
6 changed files with 193 additions and 2 deletions
+101
View File
@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace OCA\NCdiscordhook\Command;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Attribute\PublicPage;
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 OCP\PreConditionNotMetException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Enable or disable the debug endpoint.
*
* The debug endpoint exposes internal configuration and database state.
* It is disabled by default and must be explicitly enabled via this command.
*
* Usage:
* # Enable debug
* php occ ncdiscordhook:debug:enable
*
* # Disable debug
* php occ ncdiscordhook:debug:disable
*
* # Toggle (disable if enabled, enable if disabled)
* php occ ncdiscordhook:debug:toggle
*
* # Check current status
* php occ ncdiscordhook:debug:status
*/
class DebugToggle extends Command {
private const APP_ID = 'ncdiscordhook';
private const DEBUG_KEY = 'debug_enabled';
private IAppConfig $appConfig;
public function __construct(IAppConfig $appConfig) {
parent::__construct();
$this->appConfig = $appConfig;
}
protected function configure(): void {
$this
->setName('ncdiscordhook:debug:toggle')
->setDescription('Enable or disable the debug endpoint')
->addOption('enable', 'e', InputOption::VALUE_NONE, 'Enable the debug endpoint')
->addOption('disable', 'd', InputOption::VALUE_NONE, 'Disable the debug endpoint')
->addOption('status', null, InputOption::VALUE_NONE, 'Show current debug endpoint status');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$io = new SymfonyStyle($input, $output);
if ($input->getOption('status')) {
$enabled = (bool) $this->appConfig->getValueBool(self::APP_ID, self::DEBUG_KEY, false);
$io->success($enabled ? 'Debug endpoint is ENABLED' : 'Debug endpoint is DISABLED (default)');
return Command::SUCCESS;
}
if ($input->getOption('enable') && $input->getOption('disable')) {
$io->error('Cannot use both --enable and --disable at the same time.');
return Command::INVALID;
}
if ($input->getOption('enable')) {
$this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, true);
$io->warning('Debug endpoint is now ENABLED. Anyone can access /apps/ncdiscordhook/debug.');
$io->note('To disable later, run: php occ ncdiscordhook:debug:disable');
return Command::SUCCESS;
}
if ($input->getOption('disable')) {
$this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, false);
$io->success('Debug endpoint is now DISABLED.');
return Command::SUCCESS;
}
// No flag: toggle
$current = (bool) $this->appConfig->getValueBool(self::APP_ID, self::DEBUG_KEY, false);
$this->appConfig->setValueBool(self::APP_ID, self::DEBUG_KEY, !$current);
$next = !$current ? 'enabled' : 'disabled';
$io->success("Debug endpoint is now $next.");
return Command::SUCCESS;
}
}
+16
View File
@@ -361,11 +361,26 @@ class WebhookController extends Controller {
* Query params:
* - webhook_url: Full webhook URL to diagnose (e.g. /apps/ncdiscordhook/discord-webhook/{room}/{token})
* - test_post=1: Actually POST a test message to the room
*
* This endpoint is disabled by default. Enable it via:
* php occ ncdiscordhook:debug:enable
*
* SECURITY: Never leave debug enabled in production. It exposes internal
* configuration, database schema, and bot credentials.
*/
#[PublicPage]
#[NoCSRFRequired]
#[NoAdminRequired]
public function debug(): DataResponse {
// Debug endpoint must be explicitly enabled via OCC command
$debugEnabled = $this->appConfig->getValueBool('ncdiscordhook', 'debug_enabled', false);
if (!$debugEnabled) {
return new DataResponse(
['error' => 'Debug endpoint is disabled. Enable it with: php occ ncdiscordhook:debug:enable'],
Http::STATUS_FORBIDDEN,
);
}
$user = $this->userSession->getUser();
$uid = $user !== null ? $user->getUID() : null;
$isAdmin = $user !== null && $this->groupManager->isAdmin($uid);
@@ -376,6 +391,7 @@ class WebhookController extends Controller {
'user_is_admin' => $isAdmin,
'bot_user' => null,
'has_bot_password' => false,
'debug_enabled' => true,
];
try {
+17 -1
View File
@@ -704,6 +704,22 @@ class TalkService {
$richObjects[] = $richObj;
}
}
} elseif (!empty($attachment['base64'] ?? '')) {
// Base64-encoded attachment (Apprise library JSON method)
$fileData = base64_decode($attachment['base64'], true);
if ($fileData === false) {
continue;
}
$fileName = $attachment['filename'] ?? $attachment['name'] ?? 'attachment';
$mimeType = $attachment['mimetype'] ?? $attachment['mimeType'] ?? 'application/octet-stream';
$uploadPath = $this->uploadImage($roomToken, $fileName, $fileData, $mimeType);
if ($uploadPath !== null) {
$richObj = $this->buildRichObject($uploadPath, $mimeType, $roomToken);
if ($richObj !== null) {
$richObjects[] = $richObj;
}
}
}
}
}
@@ -877,7 +893,7 @@ class TalkService {
// or the message is silently dropped.
$botDisplayName = $this->getBotDisplayNameForRoom($roomToken);
// Build message body for Chat API v4
// Build message body for Chat API v1
$body = [
'message' => $message,
'actorType' => 'users',