From 2b2a875f4a25007e26eee1859dd7fc8e6c5b1cfb Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 18 May 2025 08:30:44 +0200 Subject: [PATCH 01/14] Initial working setup for browserless support Signed-off-by: SnowyLeopard --- .../Implementation/ConfigImplementation.php | 20 +++- lib/Helper/DownloadHelper.php | 6 ++ lib/Helper/UserConfigHelper.php | 69 +++++++++++--- lib/Service/HtmlDownloadService.php | 94 ++++++++++++++++++- lib/Service/RecipeService.php | 91 ++++++++++++------ src/components/Modals/SettingsDialog.vue | 46 +++++++++ src/js/api-interface.js | 7 ++ 7 files changed, 281 insertions(+), 52 deletions(-) diff --git a/lib/Controller/Implementation/ConfigImplementation.php b/lib/Controller/Implementation/ConfigImplementation.php index 49a0a2f63..ea85fb607 100644 --- a/lib/Controller/Implementation/ConfigImplementation.php +++ b/lib/Controller/Implementation/ConfigImplementation.php @@ -9,7 +9,8 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; -class ConfigImplementation { +class ConfigImplementation +{ /** @var RecipeService */ private $service; /** @var DbCacheService */ @@ -32,13 +33,15 @@ public function __construct( } protected const KEY_VISIBLE_INFO_BLOCKS = 'visibleInfoBlocks'; + protected const KEY_BROWSERLESS_ADDRESS = 'browserless_address'; /** * Get the current configuration of the app * * @return JSONResponse */ - public function list() { + public function list() + { $this->dbCacheService->triggerCheck(); return new JSONResponse([ @@ -46,6 +49,7 @@ public function list() { 'update_interval' => $this->dbCacheService->getSearchIndexUpdateInterval(), 'print_image' => $this->service->getPrintImage(), self::KEY_VISIBLE_INFO_BLOCKS => $this->service->getVisibleInfoBlocks(), + self::KEY_BROWSERLESS_ADDRESS => $this->service->getBrowserlessAddress(), ], Http::STATUS_OK); } @@ -59,7 +63,8 @@ public function list() { * * @return JSONResponse */ - public function config() { + public function config() + { $data = $this->restParser->getParameters(); if (isset($data['folder'])) { @@ -72,13 +77,17 @@ public function config() { } if (isset($data['print_image'])) { - $this->service->setPrintImage((bool)$data['print_image']); + $this->service->setPrintImage((bool) $data['print_image']); } if (isset($data[self::KEY_VISIBLE_INFO_BLOCKS])) { $this->service->setVisibleInfoBlocks($data[self::KEY_VISIBLE_INFO_BLOCKS]); } + if (isset($data[self::KEY_BROWSERLESS_ADDRESS])) { + $this->service->setBrowserlessAddress($data[self::KEY_BROWSERLESS_ADDRESS]); + } + $this->dbCacheService->triggerCheck(); return new JSONResponse('OK', Http::STATUS_OK); @@ -89,7 +98,8 @@ public function config() { * * @return JSONResponse */ - public function reindex() { + public function reindex() + { $this->dbCacheService->updateCache(); return new JSONResponse('Search index rebuilt successfully', Http::STATUS_OK); diff --git a/lib/Helper/DownloadHelper.php b/lib/Helper/DownloadHelper.php index 2e9b99bc8..8e0456fe0 100644 --- a/lib/Helper/DownloadHelper.php +++ b/lib/Helper/DownloadHelper.php @@ -67,6 +67,7 @@ public function downloadFile(string $url, array $options = [], array $headers = curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_WRITEHEADER, $hp); + curl_setopt($ch, CURLOPT_VERBOSE, true); if (!empty($headers)) { curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); @@ -74,6 +75,11 @@ public function downloadFile(string $url, array $options = [], array $headers = curl_setopt_array($ch, $options); $ret = curl_exec($ch); + $err = curl_error($ch); + + if ($err) { + echo 'cURL Error #:' . $err; + } if ($ret === false) { $ex = new NoDownloadWasCarriedOutException($this->l->t('Downloading of a file failed returned the following error message: %s', [curl_error($ch)])); diff --git a/lib/Helper/UserConfigHelper.php b/lib/Helper/UserConfigHelper.php index a4b2efe51..ab5240685 100644 --- a/lib/Helper/UserConfigHelper.php +++ b/lib/Helper/UserConfigHelper.php @@ -10,7 +10,8 @@ /** * This class allows access to the per-user configuration of the app */ -class UserConfigHelper { +class UserConfigHelper +{ /** * @var ?string */ @@ -41,13 +42,15 @@ public function __construct( protected const KEY_PRINT_IMAGE = 'print_image'; protected const KEY_VISIBLE_INFO_BLOCKS = 'visible_info_blocks'; protected const KEY_FOLDER = 'folder'; + protected const KEY_BROWSERLESS_ADDRESS = 'browserless_address'; /** * Checks if the user is logged in and the configuration can be obtained at all * * @throws UserNotLoggedInException if no user is logged in */ - private function ensureUserIsLoggedIn(): void { + private function ensureUserIsLoggedIn(): void + { if (is_null($this->userId)) { throw new UserNotLoggedInException($this->l->t('The user is not logged in. No user configuration can be obtained.')); } @@ -60,7 +63,8 @@ private function ensureUserIsLoggedIn(): void { * @return string The resulting value or '' if the key was not found * @throws UserNotLoggedInException if no user is logged in */ - private function getRawValue(string $key): string { + private function getRawValue(string $key): string + { $this->ensureUserIsLoggedIn(); return $this->config->getUserValue($this->userId, Application::APP_ID, $key); } @@ -72,7 +76,8 @@ private function getRawValue(string $key): string { * @param string $value The value of the config entry * @throws UserNotLoggedInException if no user is logged in */ - private function setRawValue(string $key, string $value): void { + private function setRawValue(string $key, string $value): void + { $this->ensureUserIsLoggedIn(); $this->config->setUserValue($this->userId, Application::APP_ID, $key, $value); } @@ -83,7 +88,8 @@ private function setRawValue(string $key, string $value): void { * @return int The timestamp of the last index rebuild * @throws UserNotLoggedInException if no user is logged in */ - public function getLastIndexUpdate(): int { + public function getLastIndexUpdate(): int + { $rawValue = $this->getRawValue(self::KEY_LAST_INDEX_UPDATE); if ($rawValue === '') { return 0; @@ -98,7 +104,8 @@ public function getLastIndexUpdate(): int { * @param int $value The timestamp of the last index rebuild * @throws UserNotLoggedInException if no user is logged in */ - public function setLastIndexUpdate(int $value): void { + public function setLastIndexUpdate(int $value): void + { $this->setRawValue(self::KEY_LAST_INDEX_UPDATE, strval($value)); } @@ -108,7 +115,8 @@ public function setLastIndexUpdate(int $value): void { * @return int The number of seconds to wait before a new rescan is triggered * @throws UserNotLoggedInException if no user is logged in */ - public function getUpdateInterval(): int { + public function getUpdateInterval(): int + { $rawValue = $this->getRawValue(self::KEY_UPDATE_INTERVAL); if ($rawValue === '') { return 5; @@ -123,8 +131,9 @@ public function getUpdateInterval(): int { * @param int $value The number of seconds to wait at least between rescans * @throws UserNotLoggedInException if no user is logged in */ - public function setUpdateInterval(int $value): void { - $this->setRawValue(self::KEY_UPDATE_INTERVAL, (string)$value); + public function setUpdateInterval(int $value): void + { + $this->setRawValue(self::KEY_UPDATE_INTERVAL, (string) $value); } /** @@ -133,7 +142,8 @@ public function setUpdateInterval(int $value): void { * @return bool true, if the image should be printed * @throws UserNotLoggedInException if no user is logged in */ - public function getPrintImage(): bool { + public function getPrintImage(): bool + { $rawValue = $this->getRawValue(self::KEY_PRINT_IMAGE); if ($rawValue === '') { return true; @@ -148,7 +158,8 @@ public function getPrintImage(): bool { * @param bool $value true if the image should be printed * @throws UserNotLoggedInException if no user is logged in */ - public function setPrintImage(bool $value): void { + public function setPrintImage(bool $value): void + { if ($value) { $this->setRawValue(self::KEY_PRINT_IMAGE, '1'); } else { @@ -162,7 +173,8 @@ public function setPrintImage(bool $value): void { * @return array keys: info block ids, values: display state * @throws UserNotLoggedInException if no user is logged in */ - public function getVisibleInfoBlocks(): array { + public function getVisibleInfoBlocks(): array + { $rawValue = $this->getRawValue(self::KEY_VISIBLE_INFO_BLOCKS); if ($rawValue === '') { @@ -184,7 +196,8 @@ public function getVisibleInfoBlocks(): array { * @param array keys: info block ids, values: display state * @throws UserNotLoggedInException if no user is logged in */ - public function setVisibleInfoBlocks(array $visibleInfoBlocks): void { + public function setVisibleInfoBlocks(array $visibleInfoBlocks): void + { $this->setRawValue(self::KEY_VISIBLE_INFO_BLOCKS, json_encode($visibleInfoBlocks)); } @@ -200,7 +213,8 @@ public function setVisibleInfoBlocks(array $visibleInfoBlocks): void { * @return string The name of the folder within the users files * @throws UserNotLoggedInException if no user is logged in */ - public function getFolderName(): string { + public function getFolderName(): string + { $rawValue = $this->getRawValue(self::KEY_FOLDER); if ($rawValue === '') { @@ -223,7 +237,32 @@ public function getFolderName(): string { * @param string $value The name of the folder within the user's files * @throws UserNotLoggedInException if no user is logged in */ - public function setFolderName(string $value): void { + public function setFolderName(string $value): void + { $this->setRawValue(self::KEY_FOLDER, $value); } + + /** + * Gets the browserless address from the configuration + * + * @return string The browserless address + * @throws UserNotLoggedInException if no user is logged in + */ + public function getBrowserlessAddress(): string + { + $rawValue = $this->getRawValue(self::KEY_BROWSERLESS_ADDRESS); + + return $rawValue; + } + + /** + * Sets the browserless address in the configuration + * + * @param string $address The browserless address to store + * @throws UserNotLoggedInException if no user is logged in + */ + public function setBrowserlessAddress(string $address): void + { + $this->setRawValue(self::KEY_BROWSERLESS_ADDRESS, $address); + } } diff --git a/lib/Service/HtmlDownloadService.php b/lib/Service/HtmlDownloadService.php index 5ce2013ef..6eb630cd4 100644 --- a/lib/Service/HtmlDownloadService.php +++ b/lib/Service/HtmlDownloadService.php @@ -13,10 +13,12 @@ use OCA\Cookbook\Helper\HTMLFilter\HtmlEncodingFilter; use OCA\Cookbook\Helper\HTMLFilter\HtmlEntityDecodeFilter; use OCA\Cookbook\Helper\HtmlToDomParser; +use OCA\Cookbook\Helper\UserConfigHelper; use OCP\IL10N; use Psr\Log\LoggerInterface; -class HtmlDownloadService { +class HtmlDownloadService +{ /** * @var array */ @@ -44,6 +46,9 @@ class HtmlDownloadService { /** @var DownloadEncodingHelper */ private $downloadEncodingHelper; + /** @var UserConfigHelper */ + private $userConfigHelper; + /** * @var DOMDocument */ @@ -58,6 +63,7 @@ public function __construct( DownloadHelper $downloadHelper, EncodingGuessingHelper $encodingGuesser, DownloadEncodingHelper $downloadEncodingHelper, + UserConfigHelper $userConfigHelper, ) { $this->htmlFilters = [ $htmlEntityDecodeFilter, @@ -69,6 +75,7 @@ public function __construct( $this->downloadHelper = $downloadHelper; $this->encodingGuesser = $encodingGuesser; $this->downloadEncodingHelper = $downloadEncodingHelper; + $this->userConfigHelper = $userConfigHelper; } /** @@ -81,8 +88,18 @@ public function __construct( * @return int The state indicating the result of the parsing (@see HtmlToDomParser) * @throws ImportException If obtaining of the URL was not possible */ - public function downloadRecipe(string $url): int { - $html = $this->fetchHtmlPage($url); + public function downloadRecipe(string $url): int + { + $browserlessAddress = $this->userConfigHelper->getBrowserlessAddress(); + + // Check if a browserless address is available + if ($browserlessAddress) { + // Use Browserless API if the address is set + $html = $this->fetchHtmlPageUsingBrowserless($url); + } else { + // Otherwise, use the standard method + $html = $this->fetchHtmlPage($url); + } // Filter the HTML code /** @var AbstractHtmlFilter $filter */ @@ -100,10 +117,76 @@ public function downloadRecipe(string $url): int { * Get the HTML docuemnt after it has been downloaded and parsed with downloadRecipe() * @return ?DOMDocument The loaded HTML document or null if document could not be loaded successfully */ - public function getDom(): ?DOMDocument { + public function getDom(): ?DOMDocument + { return $this->dom; } + /** + * Fetch an HTML page from Browserless.io or self hosted Browserless (rendered HTML) + * + * @param string $url The URL of the page to fetch + * + * @throws ImportException If the given URL was not fetched or parsed + * + * @return string The rendered HTML content as a plain string + */ + private function fetchHtmlPageUsingBrowserless(string $url): string + { + // Get the browserless address from configuration or setting + $browserlessAddress = $this->userConfigHelper->getBrowserlessAddress(); + + if (empty($browserlessAddress)) { + // Handle the case where Browserless address is not configured + $this->logger->error('Browserless address is not set.'); + throw new ImportException($this->l->t('Browserless address is not configured.')); + } + + // API endpoint for Browserless.io + $apiEndpoint = $browserlessAddress . '/chromium/content?token=AABBCCDD'; // Use the dynamic address + + // Prepare the data to be sent in the POST request + $data = json_encode([ + 'url' => $url, + 'userAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0' + ]); + + $opt = [ + CURLOPT_POSTFIELDS => $data, + CURLOPT_CUSTOMREQUEST => 'POST', + ]; + + $langCode = $this->l->getLocaleCode(); + $langCode = str_replace('_', '-', $langCode); + $headers = [ + "Accept-Language: $langCode,en;q=0.5", + 'Content-Type: application/json', + 'Cache-Control: no-cache' + ]; + + try { + $this->downloadHelper->downloadFile($apiEndpoint, $opt, $headers); + } catch (NoDownloadWasCarriedOutException $ex) { + throw new ImportException($this->l->t('Exception while downloading recipe from %s.', [$url]), 0, $ex); + } + + $status = $this->downloadHelper->getStatus(); + + if ($status < 200 || $status >= 300) { + throw new ImportException($this->l->t('Download from %s failed as HTTP status code %d is not in expected range.', [$url, $status])); + } + + $html = $this->downloadHelper->getContent(); + + // Check if the response was successful + if ($html === false) { + $this->logger->error('Failed to fetch rendered HTML from Browserless.io'); + throw new ImportException($this->l->t('Failed to fetch rendered HTML.')); + } + + return $html; + } + /** * Fetch an HTML page from the internet * @@ -113,7 +196,8 @@ public function getDom(): ?DOMDocument { * * @return string The content of the page as a plain string */ - private function fetchHtmlPage(string $url): string { + private function fetchHtmlPage(string $url): string + { $host = parse_url($url); if (!$host) { diff --git a/lib/Service/RecipeService.php b/lib/Service/RecipeService.php index c26ed514a..ecccb21b7 100755 --- a/lib/Service/RecipeService.php +++ b/lib/Service/RecipeService.php @@ -30,7 +30,8 @@ * * @package OCA\Cookbook\Service */ -class RecipeService { +class RecipeService +{ private $root; private $user_id; private $db; @@ -106,7 +107,8 @@ public function __construct( * * @return ?array */ - public function getRecipeById(int $id) { + public function getRecipeById(int $id) + { $file = $this->getRecipeFileByFolderId($id); if (!$file) { @@ -121,7 +123,8 @@ public function getRecipeById(int $id) { * * @param int $id */ - public function getRecipeMTime(int $id): ?int { + public function getRecipeMTime(int $id): ?int + { $file = $this->getRecipeFileByFolderId($id); if (!$file) { @@ -138,7 +141,8 @@ public function getRecipeMTime(int $id): ?int { * * @return File|null */ - public function getRecipeFileByFolderId(int $id) { + public function getRecipeFileByFolderId(int $id) + { $user_folder = $this->userFolder->getFolder(); $recipe_folder = $user_folder->getById($id); @@ -170,7 +174,8 @@ public function getRecipeFileByFolderId(int $id) { * * @throws Exception */ - public function checkRecipe(array $json): array { + public function checkRecipe(array $json): array + { if (!$json) { throw new Exception('Recipe array was null'); } @@ -185,7 +190,8 @@ public function checkRecipe(array $json): array { /** * @param int $id */ - public function deleteRecipe(int $id) { + public function deleteRecipe(int $id) + { $user_folder = $this->userFolder->getFolder(); $recipe_folder = $user_folder->getById($id); @@ -202,7 +208,8 @@ public function deleteRecipe(int $id) { * * @return File */ - public function addRecipe($json, $importedHtml = null) { + public function addRecipe($json, $importedHtml = null) + { if (!$json || !isset($json['name']) || !$json['name']) { throw new NoRecipeNameGivenException($this->il10n->t('No recipe name was given. A unique name is required to store the recipe.')); } @@ -323,7 +330,8 @@ public function addRecipe($json, $importedHtml = null) { return $recipe_file; } - private function downloadImage(string $url) { + private function downloadImage(string $url) + { $this->downloadHelper->downloadFile($url); $status = $this->downloadHelper->getStatus(); if ($status >= 400) { @@ -340,7 +348,8 @@ private function downloadImage(string $url) { * @throws Exception * @return File */ - public function downloadRecipe(string $url): File { + public function downloadRecipe(string $url): File + { $this->htmlDownloadService->downloadRecipe($url); try { @@ -366,7 +375,8 @@ public function downloadRecipe(string $url): File { /** * @return array */ - public function getRecipeFiles() { + public function getRecipeFiles() + { $user_folder = $this->userFolder->getFolder(); $recipe_folders = $user_folder->getDirectoryListing(); $recipe_files = []; @@ -388,7 +398,8 @@ public function getRecipeFiles() { * Updates the search index (no more) and migrate file structure * @deprecated */ - public function updateSearchIndex() { + public function updateSearchIndex() + { try { $this->migrateFolderStructure(); } catch (UserFolderNotWritableException $ex) { @@ -398,7 +409,8 @@ public function updateSearchIndex() { } } - private function migrateFolderStructure() { + private function migrateFolderStructure() + { // Remove old cache folder if needed $legacy_cache_path = '/cookbook/cache'; @@ -432,7 +444,8 @@ private function migrateFolderStructure() { * * @return array */ - public function getAllKeywordsInSearchIndex() { + public function getAllKeywordsInSearchIndex() + { return $this->db->findAllKeywords($this->user_id); } @@ -441,7 +454,8 @@ public function getAllKeywordsInSearchIndex() { * * @return array */ - public function getAllCategoriesInSearchIndex() { + public function getAllCategoriesInSearchIndex() + { return $this->db->findAllCategories($this->user_id); } @@ -451,7 +465,8 @@ public function getAllCategoriesInSearchIndex() { * * @param array $recipes */ - private function addDatesToRecipes(array &$recipes) { + private function addDatesToRecipes(array &$recipes) + { foreach ($recipes as $i => $recipe) { if (!array_key_exists('dateCreated', $recipe) || !array_key_exists('dateModified', $recipe)) { $r = $this->getRecipeById($recipe['recipe_id']); @@ -466,7 +481,8 @@ private function addDatesToRecipes(array &$recipes) { * * @return array */ - public function getAllRecipesInSearchIndex(): array { + public function getAllRecipesInSearchIndex(): array + { $recipes = $this->db->findAllRecipes($this->user_id); $this->addDatesToRecipes($recipes); @@ -480,7 +496,8 @@ public function getAllRecipesInSearchIndex(): array { * * @return array */ - public function getRecipesByCategory($category): array { + public function getRecipesByCategory($category): array + { $recipes = $this->db->getRecipesByCategory($category, $this->user_id); $this->addDatesToRecipes($recipes); @@ -495,7 +512,8 @@ public function getRecipesByCategory($category): array { * @return array * @throws DoesNotExistException */ - public function getRecipesByKeywords($keywords): array { + public function getRecipesByKeywords($keywords): array + { $recipes = $this->db->getRecipesByKeywords($keywords, $this->user_id); $this->addDatesToRecipes($recipes); @@ -512,7 +530,8 @@ public function getRecipesByKeywords($keywords): array { * @throws DoesNotExistException * */ - public function findRecipesInSearchIndex(string $keywords_string): array { + public function findRecipesInSearchIndex(string $keywords_string): array + { $keywords_string = strtolower($keywords_string); $keywords_array = []; preg_match_all('/[^ ,]+/', $keywords_string, $keywords_array); @@ -531,7 +550,8 @@ public function findRecipesInSearchIndex(string $keywords_string): array { * @param int $interval * @throws PreConditionNotMetException */ - public function setSearchIndexUpdateInterval(int $interval) { + public function setSearchIndexUpdateInterval(int $interval) + { $this->userConfigHelper->setUpdateInterval($interval); } @@ -539,7 +559,8 @@ public function setSearchIndexUpdateInterval(int $interval) { * @param bool $printImage * @throws PreConditionNotMetException */ - public function setPrintImage(bool $printImage) { + public function setPrintImage(bool $printImage) + { $this->userConfigHelper->setPrintImage($printImage); } @@ -547,7 +568,8 @@ public function setPrintImage(bool $printImage) { * Should image be printed with the recipe * @return bool */ - public function getPrintImage() { + public function getPrintImage() + { return $this->userConfigHelper->getPrintImage(); } @@ -555,7 +577,8 @@ public function getPrintImage() { * Sets which info blocks are displayed next to the recipe * @param array keys: info block ids, values: display state */ - public function setVisibleInfoBlocks(array $visibleInfoBlocks) { + public function setVisibleInfoBlocks(array $visibleInfoBlocks) + { $this->userConfigHelper->setVisibleInfoBlocks($visibleInfoBlocks); } @@ -563,14 +586,26 @@ public function setVisibleInfoBlocks(array $visibleInfoBlocks) { * Determines which info blocks are displayed next to the recipe * @return array keys: info block ids, values: display state */ - public function getVisibleInfoBlocks(): array { + public function getVisibleInfoBlocks(): array + { return $this->userConfigHelper->getVisibleInfoBlocks(); } + public function getBrowserlessAddress() + { + return $this->userConfigHelper->getBrowserlessAddress(); + } + + public function setBrowserlessAddress(string $address) + { + $this->userConfigHelper->setBrowserlessAddress($address); + } + /** * Get recipe file contents as an array */ - public function parseRecipeFile(File $file): ?array { + public function parseRecipeFile(File $file): ?array + { $json = json_decode($file->getContent(), true); if (!$json) { @@ -599,7 +634,8 @@ public function parseRecipeFile(File $file): ?array { * * @return File */ - public function getRecipeImageFileByFolderId($id, $size = 'thumb'): File { + public function getRecipeImageFileByFolderId($id, $size = 'thumb'): File + { $recipe_folders = $this->root->getById($id); if (count($recipe_folders) < 1) { throw new Exception($this->il10n->t('Recipe with ID %d not found.', [$id])); @@ -627,7 +663,8 @@ public function getRecipeImageFileByFolderId($id, $size = 'thumb'): File { * * @return bool */ - private function isRecipeFile($file) { + private function isRecipeFile($file) + { $allowedExtensions = ['json']; if ($file->getType() !== 'file') { diff --git a/src/components/Modals/SettingsDialog.vue b/src/components/Modals/SettingsDialog.vue index 7d96821d0..ee7f01b55 100644 --- a/src/components/Modals/SettingsDialog.vue +++ b/src/components/Modals/SettingsDialog.vue @@ -157,6 +157,25 @@ + +
+
    +
  • + + +
  • +
+
+
} */ const updateInterval = ref(0); +/** + * @type {import('vue').Ref} + */ +const browserlessAddress = ref(''); /** * @type {import('vue').Ref} */ @@ -384,6 +407,24 @@ const pickRecipeFolder = () => { }); }; +watch( + () => browserlessAddress.value, + async (newVal, oldVal) => { + if (!writeChanges.value) { + return; + } + try { + await api.config.browserlessAddress.update(newVal); + await store.dispatch('refreshConfig'); + } catch { + await showSimpleAlertModal( + t('cookbook', 'Could not save Browserless address'), + ); + browserlessAddress.value = oldVal; // Revert if save fails + } + } +); + /** * Reindex all recipes */ @@ -435,6 +476,7 @@ const handleShowSettings = () => { store.state.localSettings.showFiltersInRecipeList; updateInterval.value = config.update_interval; recipeFolder.value = config.folder; + browserlessAddress.value = config.browserless_address; nextTick(() => { writeChanges.value = true; @@ -470,4 +512,8 @@ export default { display: block; width: 100%; } + +#settings-section_settings-browserless-address input { + width: auto; +} diff --git a/src/js/api-interface.js b/src/js/api-interface.js index 9150bd61f..1b98d8ecb 100644 --- a/src/js/api-interface.js +++ b/src/js/api-interface.js @@ -108,6 +108,10 @@ function updateVisibleInfoBlocks(visibleInfoBlocks) { return instance.post(`${baseUrl}/config`, { visibleInfoBlocks }); } +function updateBrowserlessAddress(newAddress) { + return instance.post(`${baseUrl}/config`, { browserless_address: newAddress }); +} + function reindex() { return instance.post(`${baseUrl}/reindex`); } @@ -146,5 +150,8 @@ export default { visibleInfoBlocks: { update: updateVisibleInfoBlocks, }, + browserlessAddress: { + update: updateBrowserlessAddress, + }, }, }; From 42d5e09fc7c5c4731c3035722d757d0c31c4f5fb Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Tue, 20 May 2025 20:05:54 +0200 Subject: [PATCH 02/14] add browserless token Signed-off-by: SnowyLeopard --- .../Implementation/ConfigImplementation.php | 6 ++ lib/Helper/UserConfigHelper.php | 25 ++++++++ lib/Service/HtmlDownloadService.php | 62 ++++++++++--------- lib/Service/RecipeService.php | 10 +++ src/components/Modals/SettingsDialog.vue | 40 ++++++++++-- src/js/api-interface.js | 7 +++ 6 files changed, 116 insertions(+), 34 deletions(-) diff --git a/lib/Controller/Implementation/ConfigImplementation.php b/lib/Controller/Implementation/ConfigImplementation.php index ea85fb607..5abbafb9d 100644 --- a/lib/Controller/Implementation/ConfigImplementation.php +++ b/lib/Controller/Implementation/ConfigImplementation.php @@ -34,6 +34,7 @@ public function __construct( protected const KEY_VISIBLE_INFO_BLOCKS = 'visibleInfoBlocks'; protected const KEY_BROWSERLESS_ADDRESS = 'browserless_address'; + protected const KEY_BROWSERLESS_TOKEN = 'browserless_token'; /** * Get the current configuration of the app @@ -50,6 +51,7 @@ public function list() 'print_image' => $this->service->getPrintImage(), self::KEY_VISIBLE_INFO_BLOCKS => $this->service->getVisibleInfoBlocks(), self::KEY_BROWSERLESS_ADDRESS => $this->service->getBrowserlessAddress(), + self::KEY_BROWSERLESS_TOKEN => $this->service->getBrowserlessToken(), ], Http::STATUS_OK); } @@ -88,6 +90,10 @@ public function config() $this->service->setBrowserlessAddress($data[self::KEY_BROWSERLESS_ADDRESS]); } + if (isset($data[self::KEY_BROWSERLESS_TOKEN])) { + $this->service->setBrowserlessToken($data[self::KEY_BROWSERLESS_TOKEN]); + } + $this->dbCacheService->triggerCheck(); return new JSONResponse('OK', Http::STATUS_OK); diff --git a/lib/Helper/UserConfigHelper.php b/lib/Helper/UserConfigHelper.php index ab5240685..22dfec024 100644 --- a/lib/Helper/UserConfigHelper.php +++ b/lib/Helper/UserConfigHelper.php @@ -43,6 +43,7 @@ public function __construct( protected const KEY_VISIBLE_INFO_BLOCKS = 'visible_info_blocks'; protected const KEY_FOLDER = 'folder'; protected const KEY_BROWSERLESS_ADDRESS = 'browserless_address'; + protected const KEY_BROWSERLESS_TOKEN = 'browserless_token'; /** * Checks if the user is logged in and the configuration can be obtained at all @@ -265,4 +266,28 @@ public function setBrowserlessAddress(string $address): void { $this->setRawValue(self::KEY_BROWSERLESS_ADDRESS, $address); } + + /** + * Gets the browserless token from the configuration + * + * @return string The browserless token + * @throws UserNotLoggedInException if no user is logged in + */ + public function getBrowserlessToken(): string + { + $rawValue = $this->getRawValue(self::KEY_BROWSERLESS_TOKEN); + + return $rawValue; + } + + /** + * Sets the browserless token in the configuration + * + * @param string $token The browserless token to store + * @throws UserNotLoggedInException if no user is logged in + */ + public function setBrowserlessToken(string $token): void + { + $this->setRawValue(self::KEY_BROWSERLESS_TOKEN, $token); + } } diff --git a/lib/Service/HtmlDownloadService.php b/lib/Service/HtmlDownloadService.php index 6eb630cd4..050e57bc9 100644 --- a/lib/Service/HtmlDownloadService.php +++ b/lib/Service/HtmlDownloadService.php @@ -135,6 +135,7 @@ private function fetchHtmlPageUsingBrowserless(string $url): string { // Get the browserless address from configuration or setting $browserlessAddress = $this->userConfigHelper->getBrowserlessAddress(); + $browserlessToken = $this->userConfigHelper->getBrowserlessToken(); if (empty($browserlessAddress)) { // Handle the case where Browserless address is not configured @@ -142,41 +143,38 @@ private function fetchHtmlPageUsingBrowserless(string $url): string throw new ImportException($this->l->t('Browserless address is not configured.')); } + if (empty($browserlessToken)) { + // Handle the case where Browserless token is not configured + $this->logger->error('Browserless token is not set.'); + throw new ImportException($this->l->t('Browserless token is not configured.')); + } + // API endpoint for Browserless.io - $apiEndpoint = $browserlessAddress . '/chromium/content?token=AABBCCDD'; // Use the dynamic address + $apiEndpoint = $browserlessAddress . '/chromium/content?token=' . $browserlessToken; // Use the dynamic address + + $langCode = $this->l->getLocaleCode(); + $langCode = str_replace('_', '-', $langCode); // Prepare the data to be sent in the POST request $data = json_encode([ 'url' => $url, - 'userAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0' + 'userAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0', + 'setExtraHTTPHeaders' => [ + 'Accept-Language' => "$langCode,en;q=0.5", + ], ]); $opt = [ + CURLOPT_USERAGENT => 'Mozilla/5.0 (X11; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0', CURLOPT_POSTFIELDS => $data, CURLOPT_CUSTOMREQUEST => 'POST', ]; - $langCode = $this->l->getLocaleCode(); - $langCode = str_replace('_', '-', $langCode); $headers = [ - "Accept-Language: $langCode,en;q=0.5", 'Content-Type: application/json', - 'Cache-Control: no-cache' ]; - try { - $this->downloadHelper->downloadFile($apiEndpoint, $opt, $headers); - } catch (NoDownloadWasCarriedOutException $ex) { - throw new ImportException($this->l->t('Exception while downloading recipe from %s.', [$url]), 0, $ex); - } - - $status = $this->downloadHelper->getStatus(); - - if ($status < 200 || $status >= 300) { - throw new ImportException($this->l->t('Download from %s failed as HTTP status code %d is not in expected range.', [$url, $status])); - } - - $html = $this->downloadHelper->getContent(); + $html = $this->fetchContent($apiEndpoint, $opt, $headers); // Check if the response was successful if ($html === false) { @@ -227,8 +225,21 @@ private function fetchHtmlPage(string $url): string 'TE: trailers' ]; + $html = $this->fetchContent($url, $opt, $headers); + + try { + $enc = $this->encodingGuesser->guessEncoding($html, $this->downloadHelper->getContentType()); + $html = $this->downloadEncodingHelper->encodeToUTF8($html, $enc); + } catch (CouldNotGuessEncodingException $ex) { + $this->logger->notice($this->l->t('Could not find a valid encoding when parsing %s.', [$url])); + } + + return $html; + } + + private function fetchContent(string $url, array $options, array $headers): string { try { - $this->downloadHelper->downloadFile($url, $opt, $headers); + $this->downloadHelper->downloadFile($url, $options, $headers); } catch (NoDownloadWasCarriedOutException $ex) { throw new ImportException($this->l->t('Exception while downloading recipe from %s.', [$url]), 0, $ex); } @@ -239,15 +250,6 @@ private function fetchHtmlPage(string $url): string throw new ImportException($this->l->t('Download from %s failed as HTTP status code %d is not in expected range.', [$url, $status])); } - $html = $this->downloadHelper->getContent(); - - try { - $enc = $this->encodingGuesser->guessEncoding($html, $this->downloadHelper->getContentType()); - $html = $this->downloadEncodingHelper->encodeToUTF8($html, $enc); - } catch (CouldNotGuessEncodingException $ex) { - $this->logger->notice($this->l->t('Could not find a valid encoding when parsing %s.', [$url])); - } - - return $html; + return $this->downloadHelper->getContent(); } } diff --git a/lib/Service/RecipeService.php b/lib/Service/RecipeService.php index ecccb21b7..87951c53c 100755 --- a/lib/Service/RecipeService.php +++ b/lib/Service/RecipeService.php @@ -601,6 +601,16 @@ public function setBrowserlessAddress(string $address) $this->userConfigHelper->setBrowserlessAddress($address); } + public function getBrowserlessToken() + { + return $this->userConfigHelper->getBrowserlessToken(); + } + + public function setBrowserlessToken(string $token) + { + $this->userConfigHelper->setBrowserlessToken($token); + } + /** * Get recipe file contents as an array */ diff --git a/src/components/Modals/SettingsDialog.vue b/src/components/Modals/SettingsDialog.vue index ee7f01b55..e268edc46 100644 --- a/src/components/Modals/SettingsDialog.vue +++ b/src/components/Modals/SettingsDialog.vue @@ -158,8 +158,8 @@
@@ -167,12 +167,21 @@
  • +
  • + + +
  • @@ -273,6 +282,10 @@ const updateInterval = ref(0); * @type {import('vue').Ref} */ const browserlessAddress = ref(''); +/** + * @type {import('vue').Ref} + */ +const browserlessToken = ref(''); /** * @type {import('vue').Ref} */ @@ -425,6 +438,24 @@ watch( } ); +watch( + () => browserlessToken.value, + async (newVal, oldVal) => { + if (!writeChanges.value) { + return; + } + try { + await api.config.browserlessToken.update(newVal); + await store.dispatch('refreshConfig'); + } catch { + await showSimpleAlertModal( + t('cookbook', 'Could not save Browserless address'), + ); + browserlessToken.value = oldVal; // Revert if save fails + } + } +); + /** * Reindex all recipes */ @@ -477,6 +508,7 @@ const handleShowSettings = () => { updateInterval.value = config.update_interval; recipeFolder.value = config.folder; browserlessAddress.value = config.browserless_address; + browserlessToken.value = config.browserless_token; nextTick(() => { writeChanges.value = true; @@ -513,7 +545,7 @@ export default { width: 100%; } -#settings-section_settings-browserless-address input { +#settings-section_settings-browserless-config input { width: auto; } diff --git a/src/js/api-interface.js b/src/js/api-interface.js index 1b98d8ecb..2e7610cc9 100644 --- a/src/js/api-interface.js +++ b/src/js/api-interface.js @@ -112,6 +112,10 @@ function updateBrowserlessAddress(newAddress) { return instance.post(`${baseUrl}/config`, { browserless_address: newAddress }); } +function updateBrowserlessToken(newToken) { + return instance.post(`${baseUrl}/config`, { browserless_token: newToken }); +} + function reindex() { return instance.post(`${baseUrl}/reindex`); } @@ -153,5 +157,8 @@ export default { browserlessAddress: { update: updateBrowserlessAddress, }, + browserlessToken: { + update: updateBrowserlessToken, + }, }, }; From cf284a698bb3a063a7e1bbddebbdd399088f1f12 Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Wed, 21 May 2025 19:47:11 +0200 Subject: [PATCH 03/14] Move browserless config into one json Signed-off-by: SnowyLeopard --- .../Implementation/ConfigImplementation.php | 14 ++---- lib/Helper/UserConfigHelper.php | 49 ++++++------------- lib/Service/HtmlDownloadService.php | 15 +++--- lib/Service/RecipeService.php | 26 +++++----- src/components/Modals/SettingsDialog.vue | 24 ++++----- src/js/api-interface.js | 15 ++---- .../ConfigImplementationTest.php | 26 +++++++--- tests/Unit/Helper/UserConfigHelperTest.php | 24 +++++++++ .../Unit/Service/HtmlDownloadServiceTest.php | 7 ++- 9 files changed, 105 insertions(+), 95 deletions(-) diff --git a/lib/Controller/Implementation/ConfigImplementation.php b/lib/Controller/Implementation/ConfigImplementation.php index 5abbafb9d..753478db7 100644 --- a/lib/Controller/Implementation/ConfigImplementation.php +++ b/lib/Controller/Implementation/ConfigImplementation.php @@ -33,8 +33,7 @@ public function __construct( } protected const KEY_VISIBLE_INFO_BLOCKS = 'visibleInfoBlocks'; - protected const KEY_BROWSERLESS_ADDRESS = 'browserless_address'; - protected const KEY_BROWSERLESS_TOKEN = 'browserless_token'; + protected const KEY_BROWSERLESS_CONFIG = 'browserless_config'; /** * Get the current configuration of the app @@ -50,8 +49,7 @@ public function list() 'update_interval' => $this->dbCacheService->getSearchIndexUpdateInterval(), 'print_image' => $this->service->getPrintImage(), self::KEY_VISIBLE_INFO_BLOCKS => $this->service->getVisibleInfoBlocks(), - self::KEY_BROWSERLESS_ADDRESS => $this->service->getBrowserlessAddress(), - self::KEY_BROWSERLESS_TOKEN => $this->service->getBrowserlessToken(), + self::KEY_BROWSERLESS_CONFIG => $this->service->getBrowserlessConfig(), ], Http::STATUS_OK); } @@ -86,12 +84,8 @@ public function config() $this->service->setVisibleInfoBlocks($data[self::KEY_VISIBLE_INFO_BLOCKS]); } - if (isset($data[self::KEY_BROWSERLESS_ADDRESS])) { - $this->service->setBrowserlessAddress($data[self::KEY_BROWSERLESS_ADDRESS]); - } - - if (isset($data[self::KEY_BROWSERLESS_TOKEN])) { - $this->service->setBrowserlessToken($data[self::KEY_BROWSERLESS_TOKEN]); + if (isset($data[self::KEY_BROWSERLESS_CONFIG])) { + $this->service->setBrowserlessConfig($data[self::KEY_BROWSERLESS_CONFIG]); } $this->dbCacheService->triggerCheck(); diff --git a/lib/Helper/UserConfigHelper.php b/lib/Helper/UserConfigHelper.php index 22dfec024..5255b64df 100644 --- a/lib/Helper/UserConfigHelper.php +++ b/lib/Helper/UserConfigHelper.php @@ -42,8 +42,7 @@ public function __construct( protected const KEY_PRINT_IMAGE = 'print_image'; protected const KEY_VISIBLE_INFO_BLOCKS = 'visible_info_blocks'; protected const KEY_FOLDER = 'folder'; - protected const KEY_BROWSERLESS_ADDRESS = 'browserless_address'; - protected const KEY_BROWSERLESS_TOKEN = 'browserless_token'; + protected const KEY_BROWSERLESS_CONFIG = 'browserless_config'; /** * Checks if the user is logged in and the configuration can be obtained at all @@ -244,50 +243,34 @@ public function setFolderName(string $value): void } /** - * Gets the browserless address from the configuration + * Gets the browserless config from the configuration * - * @return string The browserless address + * @return array keys: url and token, values: url and token * @throws UserNotLoggedInException if no user is logged in */ - public function getBrowserlessAddress(): string + public function getBrowserlessConfig(): array { - $rawValue = $this->getRawValue(self::KEY_BROWSERLESS_ADDRESS); + $rawValue = $this->getRawValue(self::KEY_BROWSERLESS_CONFIG); - return $rawValue; - } + if ($rawValue === '') { + return [ + 'url' => null, + 'token' => null, + ]; + } - /** - * Sets the browserless address in the configuration - * - * @param string $address The browserless address to store - * @throws UserNotLoggedInException if no user is logged in - */ - public function setBrowserlessAddress(string $address): void - { - $this->setRawValue(self::KEY_BROWSERLESS_ADDRESS, $address); + return json_decode($rawValue, true); } /** - * Gets the browserless token from the configuration + * Sets the browserless config in the configuration * - * @return string The browserless token + * @param array keys: url and token, values: url and token * @throws UserNotLoggedInException if no user is logged in */ - public function getBrowserlessToken(): string + public function setBrowserlessConfig(array $data): void { - $rawValue = $this->getRawValue(self::KEY_BROWSERLESS_TOKEN); - - return $rawValue; + $this->setRawValue(self::KEY_BROWSERLESS_CONFIG, json_encode($data)); } - /** - * Sets the browserless token in the configuration - * - * @param string $token The browserless token to store - * @throws UserNotLoggedInException if no user is logged in - */ - public function setBrowserlessToken(string $token): void - { - $this->setRawValue(self::KEY_BROWSERLESS_TOKEN, $token); - } } diff --git a/lib/Service/HtmlDownloadService.php b/lib/Service/HtmlDownloadService.php index 050e57bc9..b1f981dbb 100644 --- a/lib/Service/HtmlDownloadService.php +++ b/lib/Service/HtmlDownloadService.php @@ -90,11 +90,11 @@ public function __construct( */ public function downloadRecipe(string $url): int { - $browserlessAddress = $this->userConfigHelper->getBrowserlessAddress(); + $browserlessConfig = $this->userConfigHelper->getBrowserlessConfig(); - // Check if a browserless address is available - if ($browserlessAddress) { - // Use Browserless API if the address is set + // Check if a browserless url is available + if (!empty($browserlessConfig['url']) && !empty($browserlessConfig['token'])) { + // Use Browserless API if the url is set $html = $this->fetchHtmlPageUsingBrowserless($url); } else { // Otherwise, use the standard method @@ -133,9 +133,10 @@ public function getDom(): ?DOMDocument */ private function fetchHtmlPageUsingBrowserless(string $url): string { - // Get the browserless address from configuration or setting - $browserlessAddress = $this->userConfigHelper->getBrowserlessAddress(); - $browserlessToken = $this->userConfigHelper->getBrowserlessToken(); + // Get the browserless config from configuration or setting + $browserlessConfig = $this->userConfigHelper->getBrowserlessConfig(); + $browserlessAddress = $browserlessConfig['url']; + $browserlessToken = $browserlessConfig['token']; if (empty($browserlessAddress)) { // Handle the case where Browserless address is not configured diff --git a/lib/Service/RecipeService.php b/lib/Service/RecipeService.php index 87951c53c..9678cdc88 100755 --- a/lib/Service/RecipeService.php +++ b/lib/Service/RecipeService.php @@ -591,24 +591,22 @@ public function getVisibleInfoBlocks(): array return $this->userConfigHelper->getVisibleInfoBlocks(); } - public function getBrowserlessAddress() - { - return $this->userConfigHelper->getBrowserlessAddress(); - } - - public function setBrowserlessAddress(string $address) - { - $this->userConfigHelper->setBrowserlessAddress($address); - } - - public function getBrowserlessToken() + /** + * Get browserless configuration + * @return array keys: url and token, values: url and token + */ + public function getBrowserlessConfig(): array { - return $this->userConfigHelper->getBrowserlessToken(); + return $this->userConfigHelper->getBrowserlessConfig(); } - public function setBrowserlessToken(string $token) + /** + * Sets browserless configuration. + * @param array keys: url and token, values: url and token + */ + public function setBrowserlessConfig(array $data) { - $this->userConfigHelper->setBrowserlessToken($token); + $this->userConfigHelper->setBrowserlessConfig($data); } /** diff --git a/src/components/Modals/SettingsDialog.vue b/src/components/Modals/SettingsDialog.vue index e268edc46..7dbee0083 100644 --- a/src/components/Modals/SettingsDialog.vue +++ b/src/components/Modals/SettingsDialog.vue @@ -165,12 +165,12 @@
    • - +
    • @@ -281,7 +281,7 @@ const updateInterval = ref(0); /** * @type {import('vue').Ref} */ -const browserlessAddress = ref(''); +const browserlessUrl = ref(''); /** * @type {import('vue').Ref} */ @@ -421,19 +421,19 @@ const pickRecipeFolder = () => { }; watch( - () => browserlessAddress.value, + () => browserlessUrl.value, async (newVal, oldVal) => { if (!writeChanges.value) { return; } try { - await api.config.browserlessAddress.update(newVal); + await api.config.browserlessConfig.update({'url': newVal, 'token': browserlessToken.value}); await store.dispatch('refreshConfig'); } catch { await showSimpleAlertModal( - t('cookbook', 'Could not save Browserless address'), + t('cookbook', 'Could not save Browserless url'), ); - browserlessAddress.value = oldVal; // Revert if save fails + browserlessUrl.value = oldVal; // Revert if save fails } } ); @@ -445,11 +445,11 @@ watch( return; } try { - await api.config.browserlessToken.update(newVal); + await api.config.browserlessConfig.update({'token': newVal, 'url': browserlessUrl.value}); await store.dispatch('refreshConfig'); } catch { await showSimpleAlertModal( - t('cookbook', 'Could not save Browserless address'), + t('cookbook', 'Could not save Browserless token'), ); browserlessToken.value = oldVal; // Revert if save fails } @@ -507,8 +507,8 @@ const handleShowSettings = () => { store.state.localSettings.showFiltersInRecipeList; updateInterval.value = config.update_interval; recipeFolder.value = config.folder; - browserlessAddress.value = config.browserless_address; - browserlessToken.value = config.browserless_token; + browserlessUrl.value = config.browserless_config.url; + browserlessToken.value = config.browserless_config.token; nextTick(() => { writeChanges.value = true; diff --git a/src/js/api-interface.js b/src/js/api-interface.js index 2e7610cc9..969371e96 100644 --- a/src/js/api-interface.js +++ b/src/js/api-interface.js @@ -108,12 +108,8 @@ function updateVisibleInfoBlocks(visibleInfoBlocks) { return instance.post(`${baseUrl}/config`, { visibleInfoBlocks }); } -function updateBrowserlessAddress(newAddress) { - return instance.post(`${baseUrl}/config`, { browserless_address: newAddress }); -} - -function updateBrowserlessToken(newToken) { - return instance.post(`${baseUrl}/config`, { browserless_token: newToken }); +function updateBrowserlessConfig(data) { + return instance.post(`${baseUrl}/config`, { browserless_config: data }); } function reindex() { @@ -154,11 +150,8 @@ export default { visibleInfoBlocks: { update: updateVisibleInfoBlocks, }, - browserlessAddress: { - update: updateBrowserlessAddress, - }, - browserlessToken: { - update: updateBrowserlessToken, + browserlessConfig: { + update: updateBrowserlessConfig, }, }, }; diff --git a/tests/Unit/Controller/Implementation/ConfigImplementationTest.php b/tests/Unit/Controller/Implementation/ConfigImplementationTest.php index e7ed49d68..b7b245d40 100644 --- a/tests/Unit/Controller/Implementation/ConfigImplementationTest.php +++ b/tests/Unit/Controller/Implementation/ConfigImplementationTest.php @@ -89,6 +89,7 @@ public function testList(): void { 'update_interval' => $interval, 'print_image' => $printImage, 'visibleInfoBlocks' => [], + 'browserless_config' => [] ]; $this->userFolder->method('getPath')->willReturn($folder); @@ -112,7 +113,7 @@ public function testList(): void { * @param mixed $printImage * @param mixed $visibleInfoBlocks */ - public function testConfig($data, $folderPath, $interval, $printImage, $visibleInfoBlocks): void { + public function testConfig($data, $folderPath, $interval, $printImage, $visibleInfoBlocks, $browserlessConfig): void { $this->restParser->method('getParameters')->willReturn($data); $this->dbCacheService->expects($this->once())->method('triggerCheck'); @@ -143,6 +144,12 @@ public function testConfig($data, $folderPath, $interval, $printImage, $visibleI $this->recipeService->expects($this->once())->method('setVisibleInfoBlocks')->with($visibleInfoBlocks); } + if (is_null($browserlessConfig)) { + $this->recipeService->expects($this->never())->method('setBrowserlessConfig'); + } else { + $this->recipeService->expects($this->once())->method('setBrowserlessConfig')->with($browserlessConfig); + } + /** * @var JSONResponse $response */ @@ -154,20 +161,24 @@ public function testConfig($data, $folderPath, $interval, $printImage, $visibleI public static function dataProviderConfig() { return [ 'noChange' => [ - [], null, null, null, null + [], null, null, null, null, null ], 'changeFolder' => [ - ['folder' => '/path/to/whatever'], '/path/to/whatever', null, null, null + ['folder' => '/path/to/whatever'], '/path/to/whatever', null, null, null, null ], 'changeinterval' => [ - ['update_interval' => 15], null, 15, null, null + ['update_interval' => 15], null, 15, null, null, null ], 'changePrint' => [ - ['print_image' => true], null, null, true, null + ['print_image' => true], null, null, true, null, null ], 'changeVisibleBlocks' => [ ['visibleInfoBlocks' => ['cooking-time' => true, 'preparation-time' => true]], - null, null, null, ['cooking-time' => true, 'preparation-time' => true] + null, null, null, ['cooking-time' => true, 'preparation-time' => true], null + ], + 'browserlessConfig' => [ + ['browserless_config' => ['url' => 'https://something.com', 'token' => '123456789']], + null, null, null, null, ['url' => 'https://something.com', 'token' => '123456789'] ], 'changeAll' => [ [ @@ -175,7 +186,8 @@ public static function dataProviderConfig() { 'update_interval' => 12, 'print_image' => false, 'visibleInfoBlocks' => ['cooking-time' => true, 'preparation-time' => true], - ], '/my/custom/path', 12, false, ['cooking-time' => true, 'preparation-time' => true] + 'browserless_config' => ['url' => 'https://something.com', 'token' => '123456789'] + ], '/my/custom/path', 12, false, ['cooking-time' => true, 'preparation-time' => true], ['url' => 'https://something.com', 'token' => '123456789'] ], ]; } diff --git a/tests/Unit/Helper/UserConfigHelperTest.php b/tests/Unit/Helper/UserConfigHelperTest.php index 9d2dab2b9..cf9994e18 100644 --- a/tests/Unit/Helper/UserConfigHelperTest.php +++ b/tests/Unit/Helper/UserConfigHelperTest.php @@ -162,4 +162,28 @@ public function testNoUser() { $this->expectException(UserNotLoggedInException::class); $this->dut->getFolderName(); } + + public function testGetBrowserlessConfig() { + $this->config->expects($this->once())->method('setUserValue')->with( + $this->userId, 'cookbook', 'browserless_config', + json_encode([ + 'url' => 'https://example.com', + 'token' => 'token', + ]) + ); + $this->dut->setBrowserlessConfig([ + 'url' => 'https://example.com', + 'token' => 'token', + ]); + + $this->config->expects($this->once())->method('getUserValue') + ->with($this->userId, 'cookbook', 'browserless_config') + ->willReturn(json_encode([ + 'url' => 'https://example.com', + 'token' => 'token', + ])); + + $this->assertEquals(['url' => 'https://example.com', 'token' => 'token'], $this->dut->getBrowserlessConfig()); + + } } diff --git a/tests/Unit/Service/HtmlDownloadServiceTest.php b/tests/Unit/Service/HtmlDownloadServiceTest.php index 195f6005c..c0f86a336 100644 --- a/tests/Unit/Service/HtmlDownloadServiceTest.php +++ b/tests/Unit/Service/HtmlDownloadServiceTest.php @@ -8,6 +8,7 @@ use OCA\Cookbook\Exception\NoDownloadWasCarriedOutException; use OCA\Cookbook\Helper\DownloadEncodingHelper; use OCA\Cookbook\Helper\DownloadHelper; +use OCA\Cookbook\Helper\UserConfigHelper; use OCA\Cookbook\Helper\EncodingGuessingHelper; use OCA\Cookbook\Helper\HTMLFilter\HtmlEncodingFilter; use OCA\Cookbook\Helper\HTMLFilter\HtmlEntityDecodeFilter; @@ -47,6 +48,9 @@ class HtmlDownloadServiceTest extends TestCase { /** @var DownloadEncodingHelper|MockObject */ private $downloadEncodingHelper; + /** @var \OCA\Cookbook\Helper\UserConfigHelper|MockObject */ + private $userConfigHelper; + /** * @var HtmlDownloadService */ @@ -73,10 +77,11 @@ public function setUp(): void { $this->downloadHelper = $this->createMock(DownloadHelper::class); $this->encodingGuesser = $this->createMock(EncodingGuessingHelper::class); $this->downloadEncodingHelper = $this->createMock(DownloadEncodingHelper::class); + $this->userConfigHelper = $this->createMock(UserConfigHelper::class); $this->sut = new HtmlDownloadService( $this->htmlEntityDecodeFilter, $this->htmlEncodingFilter, $this->il10n, $logger, $this->htmlParser, - $this->downloadHelper, $this->encodingGuesser, $this->downloadEncodingHelper); + $this->downloadHelper, $this->encodingGuesser, $this->downloadEncodingHelper, $this->userConfigHelper); } public function testDownloadInvalidUrl() { From 71238bc3d0b34b5003b9bf2e8474583f11b4cc4a Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 25 May 2025 11:14:47 +0200 Subject: [PATCH 04/14] Fix linting Signed-off-by: SnowyLeopard --- .../Implementation/ConfigImplementation.php | 12 +-- lib/Helper/DownloadHelper.php | 16 ++-- lib/Helper/UserConfigHelper.php | 49 ++++------ lib/Service/HtmlDownloadService.php | 15 +-- lib/Service/RecipeService.php | 91 ++++++------------- 5 files changed, 64 insertions(+), 119 deletions(-) diff --git a/lib/Controller/Implementation/ConfigImplementation.php b/lib/Controller/Implementation/ConfigImplementation.php index 753478db7..fdca1dbf0 100644 --- a/lib/Controller/Implementation/ConfigImplementation.php +++ b/lib/Controller/Implementation/ConfigImplementation.php @@ -9,8 +9,7 @@ use OCP\AppFramework\Http; use OCP\AppFramework\Http\JSONResponse; -class ConfigImplementation -{ +class ConfigImplementation { /** @var RecipeService */ private $service; /** @var DbCacheService */ @@ -40,8 +39,7 @@ public function __construct( * * @return JSONResponse */ - public function list() - { + public function list() { $this->dbCacheService->triggerCheck(); return new JSONResponse([ @@ -63,8 +61,7 @@ public function list() * * @return JSONResponse */ - public function config() - { + public function config() { $data = $this->restParser->getParameters(); if (isset($data['folder'])) { @@ -98,8 +95,7 @@ public function config() * * @return JSONResponse */ - public function reindex() - { + public function reindex() { $this->dbCacheService->updateCache(); return new JSONResponse('Search index rebuilt successfully', Http::STATUS_OK); diff --git a/lib/Helper/DownloadHelper.php b/lib/Helper/DownloadHelper.php index 8e0456fe0..25029fb04 100644 --- a/lib/Helper/DownloadHelper.php +++ b/lib/Helper/DownloadHelper.php @@ -8,7 +8,8 @@ /** * This class is mainly a wrapper for cURL and its PHP extension to download files/content from the internet. */ -class DownloadHelper { +class DownloadHelper +{ /** * Flag that indicates if a download has successfully carried out * @@ -57,7 +58,8 @@ public function __construct( * @param array $headers Additinal headers to be sent to the server * @throws NoDownloadWasCarriedOutException if the download fails for some reason */ - public function downloadFile(string $url, array $options = [], array $headers = []): void { + public function downloadFile(string $url, array $options = [], array $headers = []): void + { $this->downloaded = false; $ch = curl_init($url); @@ -67,7 +69,6 @@ public function downloadFile(string $url, array $options = [], array $headers = curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_WRITEHEADER, $hp); - curl_setopt($ch, CURLOPT_VERBOSE, true); if (!empty($headers)) { curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); @@ -111,7 +112,8 @@ public function downloadFile(string $url, array $options = [], array $headers = * * @throws NoDownloadWasCarriedOutException if there was no successful download carried out before calling this method. */ - public function getContent(): string { + public function getContent(): string + { if ($this->downloaded) { return $this->content; } else { @@ -127,7 +129,8 @@ public function getContent(): string { * @return ?string The content of the Content-Type header or null if no Content-Type header was found * @throws NoDownloadWasCarriedOutException if there was no successful download carried out before calling this method. */ - public function getContentType(): ?string { + public function getContentType(): ?string + { if (!$this->downloaded) { throw new NoDownloadWasCarriedOutException(); } @@ -151,7 +154,8 @@ public function getContentType(): ?string { * * @throws NoDownloadWasCarriedOutException if there was no successful download carried out before calling this method. */ - public function getStatus(): int { + public function getStatus(): int + { if (!$this->downloaded) { throw new NoDownloadWasCarriedOutException(); } diff --git a/lib/Helper/UserConfigHelper.php b/lib/Helper/UserConfigHelper.php index 5255b64df..89f7ca353 100644 --- a/lib/Helper/UserConfigHelper.php +++ b/lib/Helper/UserConfigHelper.php @@ -10,8 +10,7 @@ /** * This class allows access to the per-user configuration of the app */ -class UserConfigHelper -{ +class UserConfigHelper { /** * @var ?string */ @@ -49,8 +48,7 @@ public function __construct( * * @throws UserNotLoggedInException if no user is logged in */ - private function ensureUserIsLoggedIn(): void - { + private function ensureUserIsLoggedIn(): void { if (is_null($this->userId)) { throw new UserNotLoggedInException($this->l->t('The user is not logged in. No user configuration can be obtained.')); } @@ -63,8 +61,7 @@ private function ensureUserIsLoggedIn(): void * @return string The resulting value or '' if the key was not found * @throws UserNotLoggedInException if no user is logged in */ - private function getRawValue(string $key): string - { + private function getRawValue(string $key): string { $this->ensureUserIsLoggedIn(); return $this->config->getUserValue($this->userId, Application::APP_ID, $key); } @@ -76,8 +73,7 @@ private function getRawValue(string $key): string * @param string $value The value of the config entry * @throws UserNotLoggedInException if no user is logged in */ - private function setRawValue(string $key, string $value): void - { + private function setRawValue(string $key, string $value): void { $this->ensureUserIsLoggedIn(); $this->config->setUserValue($this->userId, Application::APP_ID, $key, $value); } @@ -88,8 +84,7 @@ private function setRawValue(string $key, string $value): void * @return int The timestamp of the last index rebuild * @throws UserNotLoggedInException if no user is logged in */ - public function getLastIndexUpdate(): int - { + public function getLastIndexUpdate(): int { $rawValue = $this->getRawValue(self::KEY_LAST_INDEX_UPDATE); if ($rawValue === '') { return 0; @@ -104,8 +99,7 @@ public function getLastIndexUpdate(): int * @param int $value The timestamp of the last index rebuild * @throws UserNotLoggedInException if no user is logged in */ - public function setLastIndexUpdate(int $value): void - { + public function setLastIndexUpdate(int $value): void { $this->setRawValue(self::KEY_LAST_INDEX_UPDATE, strval($value)); } @@ -115,8 +109,7 @@ public function setLastIndexUpdate(int $value): void * @return int The number of seconds to wait before a new rescan is triggered * @throws UserNotLoggedInException if no user is logged in */ - public function getUpdateInterval(): int - { + public function getUpdateInterval(): int { $rawValue = $this->getRawValue(self::KEY_UPDATE_INTERVAL); if ($rawValue === '') { return 5; @@ -131,8 +124,7 @@ public function getUpdateInterval(): int * @param int $value The number of seconds to wait at least between rescans * @throws UserNotLoggedInException if no user is logged in */ - public function setUpdateInterval(int $value): void - { + public function setUpdateInterval(int $value): void { $this->setRawValue(self::KEY_UPDATE_INTERVAL, (string) $value); } @@ -142,8 +134,7 @@ public function setUpdateInterval(int $value): void * @return bool true, if the image should be printed * @throws UserNotLoggedInException if no user is logged in */ - public function getPrintImage(): bool - { + public function getPrintImage(): bool { $rawValue = $this->getRawValue(self::KEY_PRINT_IMAGE); if ($rawValue === '') { return true; @@ -158,8 +149,7 @@ public function getPrintImage(): bool * @param bool $value true if the image should be printed * @throws UserNotLoggedInException if no user is logged in */ - public function setPrintImage(bool $value): void - { + public function setPrintImage(bool $value): void { if ($value) { $this->setRawValue(self::KEY_PRINT_IMAGE, '1'); } else { @@ -173,8 +163,7 @@ public function setPrintImage(bool $value): void * @return array keys: info block ids, values: display state * @throws UserNotLoggedInException if no user is logged in */ - public function getVisibleInfoBlocks(): array - { + public function getVisibleInfoBlocks(): array { $rawValue = $this->getRawValue(self::KEY_VISIBLE_INFO_BLOCKS); if ($rawValue === '') { @@ -196,8 +185,7 @@ public function getVisibleInfoBlocks(): array * @param array keys: info block ids, values: display state * @throws UserNotLoggedInException if no user is logged in */ - public function setVisibleInfoBlocks(array $visibleInfoBlocks): void - { + public function setVisibleInfoBlocks(array $visibleInfoBlocks): void { $this->setRawValue(self::KEY_VISIBLE_INFO_BLOCKS, json_encode($visibleInfoBlocks)); } @@ -213,8 +201,7 @@ public function setVisibleInfoBlocks(array $visibleInfoBlocks): void * @return string The name of the folder within the users files * @throws UserNotLoggedInException if no user is logged in */ - public function getFolderName(): string - { + public function getFolderName(): string { $rawValue = $this->getRawValue(self::KEY_FOLDER); if ($rawValue === '') { @@ -237,8 +224,7 @@ public function getFolderName(): string * @param string $value The name of the folder within the user's files * @throws UserNotLoggedInException if no user is logged in */ - public function setFolderName(string $value): void - { + public function setFolderName(string $value): void { $this->setRawValue(self::KEY_FOLDER, $value); } @@ -248,8 +234,7 @@ public function setFolderName(string $value): void * @return array keys: url and token, values: url and token * @throws UserNotLoggedInException if no user is logged in */ - public function getBrowserlessConfig(): array - { + public function getBrowserlessConfig(): array { $rawValue = $this->getRawValue(self::KEY_BROWSERLESS_CONFIG); if ($rawValue === '') { @@ -268,9 +253,7 @@ public function getBrowserlessConfig(): array * @param array keys: url and token, values: url and token * @throws UserNotLoggedInException if no user is logged in */ - public function setBrowserlessConfig(array $data): void - { + public function setBrowserlessConfig(array $data): void { $this->setRawValue(self::KEY_BROWSERLESS_CONFIG, json_encode($data)); } - } diff --git a/lib/Service/HtmlDownloadService.php b/lib/Service/HtmlDownloadService.php index b1f981dbb..aad2616ea 100644 --- a/lib/Service/HtmlDownloadService.php +++ b/lib/Service/HtmlDownloadService.php @@ -17,8 +17,7 @@ use OCP\IL10N; use Psr\Log\LoggerInterface; -class HtmlDownloadService -{ +class HtmlDownloadService { /** * @var array */ @@ -88,8 +87,7 @@ public function __construct( * @return int The state indicating the result of the parsing (@see HtmlToDomParser) * @throws ImportException If obtaining of the URL was not possible */ - public function downloadRecipe(string $url): int - { + public function downloadRecipe(string $url): int { $browserlessConfig = $this->userConfigHelper->getBrowserlessConfig(); // Check if a browserless url is available @@ -117,8 +115,7 @@ public function downloadRecipe(string $url): int * Get the HTML docuemnt after it has been downloaded and parsed with downloadRecipe() * @return ?DOMDocument The loaded HTML document or null if document could not be loaded successfully */ - public function getDom(): ?DOMDocument - { + public function getDom(): ?DOMDocument { return $this->dom; } @@ -131,8 +128,7 @@ public function getDom(): ?DOMDocument * * @return string The rendered HTML content as a plain string */ - private function fetchHtmlPageUsingBrowserless(string $url): string - { + private function fetchHtmlPageUsingBrowserless(string $url): string { // Get the browserless config from configuration or setting $browserlessConfig = $this->userConfigHelper->getBrowserlessConfig(); $browserlessAddress = $browserlessConfig['url']; @@ -195,8 +191,7 @@ private function fetchHtmlPageUsingBrowserless(string $url): string * * @return string The content of the page as a plain string */ - private function fetchHtmlPage(string $url): string - { + private function fetchHtmlPage(string $url): string { $host = parse_url($url); if (!$host) { diff --git a/lib/Service/RecipeService.php b/lib/Service/RecipeService.php index 9678cdc88..e476f11d5 100755 --- a/lib/Service/RecipeService.php +++ b/lib/Service/RecipeService.php @@ -30,8 +30,7 @@ * * @package OCA\Cookbook\Service */ -class RecipeService -{ +class RecipeService { private $root; private $user_id; private $db; @@ -107,8 +106,7 @@ public function __construct( * * @return ?array */ - public function getRecipeById(int $id) - { + public function getRecipeById(int $id) { $file = $this->getRecipeFileByFolderId($id); if (!$file) { @@ -123,8 +121,7 @@ public function getRecipeById(int $id) * * @param int $id */ - public function getRecipeMTime(int $id): ?int - { + public function getRecipeMTime(int $id): ?int { $file = $this->getRecipeFileByFolderId($id); if (!$file) { @@ -141,8 +138,7 @@ public function getRecipeMTime(int $id): ?int * * @return File|null */ - public function getRecipeFileByFolderId(int $id) - { + public function getRecipeFileByFolderId(int $id) { $user_folder = $this->userFolder->getFolder(); $recipe_folder = $user_folder->getById($id); @@ -174,8 +170,7 @@ public function getRecipeFileByFolderId(int $id) * * @throws Exception */ - public function checkRecipe(array $json): array - { + public function checkRecipe(array $json): array { if (!$json) { throw new Exception('Recipe array was null'); } @@ -190,8 +185,7 @@ public function checkRecipe(array $json): array /** * @param int $id */ - public function deleteRecipe(int $id) - { + public function deleteRecipe(int $id) { $user_folder = $this->userFolder->getFolder(); $recipe_folder = $user_folder->getById($id); @@ -208,8 +202,7 @@ public function deleteRecipe(int $id) * * @return File */ - public function addRecipe($json, $importedHtml = null) - { + public function addRecipe($json, $importedHtml = null) { if (!$json || !isset($json['name']) || !$json['name']) { throw new NoRecipeNameGivenException($this->il10n->t('No recipe name was given. A unique name is required to store the recipe.')); } @@ -246,7 +239,6 @@ public function addRecipe($json, $importedHtml = null) $recipe_folder->move($new_path); } - } else { // This is a new recipe, create it $json['dateCreated'] = $now; @@ -297,7 +289,6 @@ public function addRecipe($json, $importedHtml = null) $this->logger->warning('Failed to download an image using curl. Falling back to PHP default behavior.'); $full_image_data = file_get_contents($json['image']); } - } else { // The image is a local path try { @@ -308,7 +299,6 @@ public function addRecipe($json, $importedHtml = null) } } } - } else { // The image field was empty, remove images in the recipe folder $this->imageService->dropImage($recipe_folder); @@ -330,8 +320,7 @@ public function addRecipe($json, $importedHtml = null) return $recipe_file; } - private function downloadImage(string $url) - { + private function downloadImage(string $url) { $this->downloadHelper->downloadFile($url); $status = $this->downloadHelper->getStatus(); if ($status >= 400) { @@ -348,8 +337,7 @@ private function downloadImage(string $url) * @throws Exception * @return File */ - public function downloadRecipe(string $url): File - { + public function downloadRecipe(string $url): File { $this->htmlDownloadService->downloadRecipe($url); try { @@ -375,8 +363,7 @@ public function downloadRecipe(string $url): File /** * @return array */ - public function getRecipeFiles() - { + public function getRecipeFiles() { $user_folder = $this->userFolder->getFolder(); $recipe_folders = $user_folder->getDirectoryListing(); $recipe_files = []; @@ -398,8 +385,7 @@ public function getRecipeFiles() * Updates the search index (no more) and migrate file structure * @deprecated */ - public function updateSearchIndex() - { + public function updateSearchIndex() { try { $this->migrateFolderStructure(); } catch (UserFolderNotWritableException $ex) { @@ -409,8 +395,7 @@ public function updateSearchIndex() } } - private function migrateFolderStructure() - { + private function migrateFolderStructure() { // Remove old cache folder if needed $legacy_cache_path = '/cookbook/cache'; @@ -431,7 +416,6 @@ private function migrateFolderStructure() $recipe_folder = $user_folder->newFolder($recipe_name); $node->move($recipe_folder->getPath() . '/recipe.json'); - } elseif ($node instanceof Folder && strpos($node->getName(), '.json')) { // Rename folders with .json extensions (this was likely caused by a migration bug) $node->move(str_replace('.json', '', $node->getPath())); @@ -444,8 +428,7 @@ private function migrateFolderStructure() * * @return array */ - public function getAllKeywordsInSearchIndex() - { + public function getAllKeywordsInSearchIndex() { return $this->db->findAllKeywords($this->user_id); } @@ -454,8 +437,7 @@ public function getAllKeywordsInSearchIndex() * * @return array */ - public function getAllCategoriesInSearchIndex() - { + public function getAllCategoriesInSearchIndex() { return $this->db->findAllCategories($this->user_id); } @@ -465,8 +447,7 @@ public function getAllCategoriesInSearchIndex() * * @param array $recipes */ - private function addDatesToRecipes(array &$recipes) - { + private function addDatesToRecipes(array &$recipes) { foreach ($recipes as $i => $recipe) { if (!array_key_exists('dateCreated', $recipe) || !array_key_exists('dateModified', $recipe)) { $r = $this->getRecipeById($recipe['recipe_id']); @@ -481,8 +462,7 @@ private function addDatesToRecipes(array &$recipes) * * @return array */ - public function getAllRecipesInSearchIndex(): array - { + public function getAllRecipesInSearchIndex(): array { $recipes = $this->db->findAllRecipes($this->user_id); $this->addDatesToRecipes($recipes); @@ -496,8 +476,7 @@ public function getAllRecipesInSearchIndex(): array * * @return array */ - public function getRecipesByCategory($category): array - { + public function getRecipesByCategory($category): array { $recipes = $this->db->getRecipesByCategory($category, $this->user_id); $this->addDatesToRecipes($recipes); @@ -512,8 +491,7 @@ public function getRecipesByCategory($category): array * @return array * @throws DoesNotExistException */ - public function getRecipesByKeywords($keywords): array - { + public function getRecipesByKeywords($keywords): array { $recipes = $this->db->getRecipesByKeywords($keywords, $this->user_id); $this->addDatesToRecipes($recipes); @@ -530,8 +508,7 @@ public function getRecipesByKeywords($keywords): array * @throws DoesNotExistException * */ - public function findRecipesInSearchIndex(string $keywords_string): array - { + public function findRecipesInSearchIndex(string $keywords_string): array { $keywords_string = strtolower($keywords_string); $keywords_array = []; preg_match_all('/[^ ,]+/', $keywords_string, $keywords_array); @@ -550,8 +527,7 @@ public function findRecipesInSearchIndex(string $keywords_string): array * @param int $interval * @throws PreConditionNotMetException */ - public function setSearchIndexUpdateInterval(int $interval) - { + public function setSearchIndexUpdateInterval(int $interval) { $this->userConfigHelper->setUpdateInterval($interval); } @@ -559,8 +535,7 @@ public function setSearchIndexUpdateInterval(int $interval) * @param bool $printImage * @throws PreConditionNotMetException */ - public function setPrintImage(bool $printImage) - { + public function setPrintImage(bool $printImage) { $this->userConfigHelper->setPrintImage($printImage); } @@ -568,8 +543,7 @@ public function setPrintImage(bool $printImage) * Should image be printed with the recipe * @return bool */ - public function getPrintImage() - { + public function getPrintImage() { return $this->userConfigHelper->getPrintImage(); } @@ -577,8 +551,7 @@ public function getPrintImage() * Sets which info blocks are displayed next to the recipe * @param array keys: info block ids, values: display state */ - public function setVisibleInfoBlocks(array $visibleInfoBlocks) - { + public function setVisibleInfoBlocks(array $visibleInfoBlocks) { $this->userConfigHelper->setVisibleInfoBlocks($visibleInfoBlocks); } @@ -586,8 +559,7 @@ public function setVisibleInfoBlocks(array $visibleInfoBlocks) * Determines which info blocks are displayed next to the recipe * @return array keys: info block ids, values: display state */ - public function getVisibleInfoBlocks(): array - { + public function getVisibleInfoBlocks(): array { return $this->userConfigHelper->getVisibleInfoBlocks(); } @@ -595,8 +567,7 @@ public function getVisibleInfoBlocks(): array * Get browserless configuration * @return array keys: url and token, values: url and token */ - public function getBrowserlessConfig(): array - { + public function getBrowserlessConfig(): array { return $this->userConfigHelper->getBrowserlessConfig(); } @@ -604,16 +575,14 @@ public function getBrowserlessConfig(): array * Sets browserless configuration. * @param array keys: url and token, values: url and token */ - public function setBrowserlessConfig(array $data) - { + public function setBrowserlessConfig(array $data) { $this->userConfigHelper->setBrowserlessConfig($data); } /** * Get recipe file contents as an array */ - public function parseRecipeFile(File $file): ?array - { + public function parseRecipeFile(File $file): ?array { $json = json_decode($file->getContent(), true); if (!$json) { @@ -642,8 +611,7 @@ public function parseRecipeFile(File $file): ?array * * @return File */ - public function getRecipeImageFileByFolderId($id, $size = 'thumb'): File - { + public function getRecipeImageFileByFolderId($id, $size = 'thumb'): File { $recipe_folders = $this->root->getById($id); if (count($recipe_folders) < 1) { throw new Exception($this->il10n->t('Recipe with ID %d not found.', [$id])); @@ -671,8 +639,7 @@ public function getRecipeImageFileByFolderId($id, $size = 'thumb'): File * * @return bool */ - private function isRecipeFile($file) - { + private function isRecipeFile($file) { $allowedExtensions = ['json']; if ($file->getType() !== 'file') { From e9b2c5cfdaff6fd4bd1680ac163c9339bde5f186 Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 25 May 2025 11:17:02 +0200 Subject: [PATCH 05/14] Another lint fix Signed-off-by: SnowyLeopard --- lib/Controller/Implementation/ConfigImplementation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Controller/Implementation/ConfigImplementation.php b/lib/Controller/Implementation/ConfigImplementation.php index fdca1dbf0..1998b2e12 100644 --- a/lib/Controller/Implementation/ConfigImplementation.php +++ b/lib/Controller/Implementation/ConfigImplementation.php @@ -74,7 +74,7 @@ public function config() { } if (isset($data['print_image'])) { - $this->service->setPrintImage((bool) $data['print_image']); + $this->service->setPrintImage((bool)$data['print_image']); } if (isset($data[self::KEY_VISIBLE_INFO_BLOCKS])) { From a05b5e0176ace1f59241b8fe1cb108ffeecd59a6 Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 25 May 2025 11:20:10 +0200 Subject: [PATCH 06/14] more linting Signed-off-by: SnowyLeopard --- lib/Helper/DownloadHelper.php | 15 +++++---------- lib/Helper/UserConfigHelper.php | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/Helper/DownloadHelper.php b/lib/Helper/DownloadHelper.php index 25029fb04..353d7dc3a 100644 --- a/lib/Helper/DownloadHelper.php +++ b/lib/Helper/DownloadHelper.php @@ -8,8 +8,7 @@ /** * This class is mainly a wrapper for cURL and its PHP extension to download files/content from the internet. */ -class DownloadHelper -{ +class DownloadHelper { /** * Flag that indicates if a download has successfully carried out * @@ -58,8 +57,7 @@ public function __construct( * @param array $headers Additinal headers to be sent to the server * @throws NoDownloadWasCarriedOutException if the download fails for some reason */ - public function downloadFile(string $url, array $options = [], array $headers = []): void - { + public function downloadFile(string $url, array $options = [], array $headers = []): void { $this->downloaded = false; $ch = curl_init($url); @@ -112,8 +110,7 @@ public function downloadFile(string $url, array $options = [], array $headers = * * @throws NoDownloadWasCarriedOutException if there was no successful download carried out before calling this method. */ - public function getContent(): string - { + public function getContent(): string { if ($this->downloaded) { return $this->content; } else { @@ -129,8 +126,7 @@ public function getContent(): string * @return ?string The content of the Content-Type header or null if no Content-Type header was found * @throws NoDownloadWasCarriedOutException if there was no successful download carried out before calling this method. */ - public function getContentType(): ?string - { + public function getContentType(): ?string { if (!$this->downloaded) { throw new NoDownloadWasCarriedOutException(); } @@ -154,8 +150,7 @@ public function getContentType(): ?string * * @throws NoDownloadWasCarriedOutException if there was no successful download carried out before calling this method. */ - public function getStatus(): int - { + public function getStatus(): int { if (!$this->downloaded) { throw new NoDownloadWasCarriedOutException(); } diff --git a/lib/Helper/UserConfigHelper.php b/lib/Helper/UserConfigHelper.php index 89f7ca353..6eee0479d 100644 --- a/lib/Helper/UserConfigHelper.php +++ b/lib/Helper/UserConfigHelper.php @@ -125,7 +125,7 @@ public function getUpdateInterval(): int { * @throws UserNotLoggedInException if no user is logged in */ public function setUpdateInterval(int $value): void { - $this->setRawValue(self::KEY_UPDATE_INTERVAL, (string) $value); + $this->setRawValue(self::KEY_UPDATE_INTERVAL, (string)$value); } /** From 5490c84b44ee3ddf30a252eceea79d06c4f7775f Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 25 May 2025 12:12:00 +0200 Subject: [PATCH 07/14] Add test, also use encoding check for browserless download Signed-off-by: SnowyLeopard --- lib/Service/HtmlDownloadService.php | 32 ++++------ .../Unit/Service/HtmlDownloadServiceTest.php | 61 +++++++++++++++++-- 2 files changed, 69 insertions(+), 24 deletions(-) diff --git a/lib/Service/HtmlDownloadService.php b/lib/Service/HtmlDownloadService.php index aad2616ea..d6017d90f 100644 --- a/lib/Service/HtmlDownloadService.php +++ b/lib/Service/HtmlDownloadService.php @@ -171,15 +171,7 @@ private function fetchHtmlPageUsingBrowserless(string $url): string { 'Content-Type: application/json', ]; - $html = $this->fetchContent($apiEndpoint, $opt, $headers); - - // Check if the response was successful - if ($html === false) { - $this->logger->error('Failed to fetch rendered HTML from Browserless.io'); - throw new ImportException($this->l->t('Failed to fetch rendered HTML.')); - } - - return $html; + return $this->fetchContent($apiEndpoint, $opt, $headers); } /** @@ -221,16 +213,7 @@ private function fetchHtmlPage(string $url): string { 'TE: trailers' ]; - $html = $this->fetchContent($url, $opt, $headers); - - try { - $enc = $this->encodingGuesser->guessEncoding($html, $this->downloadHelper->getContentType()); - $html = $this->downloadEncodingHelper->encodeToUTF8($html, $enc); - } catch (CouldNotGuessEncodingException $ex) { - $this->logger->notice($this->l->t('Could not find a valid encoding when parsing %s.', [$url])); - } - - return $html; + return $this->fetchContent($url, $opt, $headers); } private function fetchContent(string $url, array $options, array $headers): string { @@ -246,6 +229,15 @@ private function fetchContent(string $url, array $options, array $headers): stri throw new ImportException($this->l->t('Download from %s failed as HTTP status code %d is not in expected range.', [$url, $status])); } - return $this->downloadHelper->getContent(); + $html = $this->downloadHelper->getContent(); + + try { + $enc = $this->encodingGuesser->guessEncoding($html, $this->downloadHelper->getContentType()); + $html = $this->downloadEncodingHelper->encodeToUTF8($html, $enc); + } catch (CouldNotGuessEncodingException $ex) { + $this->logger->notice($this->l->t('Could not find a valid encoding when parsing %s.', [$url])); + } + + return $html; } } diff --git a/tests/Unit/Service/HtmlDownloadServiceTest.php b/tests/Unit/Service/HtmlDownloadServiceTest.php index c0f86a336..f7003a5ba 100644 --- a/tests/Unit/Service/HtmlDownloadServiceTest.php +++ b/tests/Unit/Service/HtmlDownloadServiceTest.php @@ -80,8 +80,16 @@ public function setUp(): void { $this->userConfigHelper = $this->createMock(UserConfigHelper::class); $this->sut = new HtmlDownloadService( - $this->htmlEntityDecodeFilter, $this->htmlEncodingFilter, $this->il10n, $logger, $this->htmlParser, - $this->downloadHelper, $this->encodingGuesser, $this->downloadEncodingHelper, $this->userConfigHelper); + $this->htmlEntityDecodeFilter, + $this->htmlEncodingFilter, + $this->il10n, + $logger, + $this->htmlParser, + $this->downloadHelper, + $this->encodingGuesser, + $this->downloadEncodingHelper, + $this->userConfigHelper + ); } public function testDownloadInvalidUrl() { @@ -101,7 +109,10 @@ public function testDownloadFailing() { public static function dpBadStatus() { return [ - [180], [199], [300], [404] + [180], + [199], + [300], + [404] ]; } @@ -127,7 +138,7 @@ public function testDownload() { $encoding = 'utf-8'; $this->downloadHelper->expects($this->once()) - ->method('downloadFile'); + ->method('downloadFile')->with($url, $this->anything(), $this->anything()); $this->downloadHelper->method('getStatus')->willReturn(200); $this->downloadHelper->method('getContent')->willReturn($content); $this->downloadHelper->method('getContentType')->willReturn($contentType); @@ -183,4 +194,46 @@ public function testDownloadWithoutEncoding() { $this->assertSame($dom, $this->sut->getDom()); } + + public function testDownloadWithBrowserless() { + $url = 'http://example.com'; + $content = 'The content of the html file'; + $dom = $this->createStub(DOMDocument::class); + $state = 12345; + $contentType = 'The content type'; + $encoding = 'utf-8'; + $browserlessUrl = "http://browserless.url/chromium/content?token=token"; + + $this->downloadHelper->expects($this->once()) + ->method('downloadFile')->with($browserlessUrl, $this->anything(), $this->anything()); + + $this->il10n->method('getLocaleCode')->willReturn('en-US'); + $this->userConfigHelper->method('getBrowserlessConfig')->willReturn([ + 'url' => "http://browserless.url", + 'token' => 'token', + ]); + + $this->downloadHelper->method('getStatus')->willReturn(200); + $this->downloadHelper->method('getContent')->willReturn($content); + $this->downloadHelper->method('getContentType')->willReturn($contentType); + + $this->encodingGuesser->method('guessEncoding') + ->with($content, $contentType) + ->willReturn($encoding); + $this->downloadEncodingHelper->method('encodeToUTF8') + ->with($content, $encoding)->willReturnArgument(0); + + $this->htmlParser->expects($this->once())->method('loadHtmlString') + ->with( + $this->anything(), + $this->equalTo($url), + $this->equalTo($content) + )->willReturn($dom); + $this->htmlParser->method('getState')->willReturn($state); + + $ret = $this->sut->downloadRecipe($url); + $this->assertEquals($state, $ret); + + $this->assertSame($dom, $this->sut->getDom()); + } } From 1096a12f0f065755abeaca6fcae99a9699de8ff5 Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 25 May 2025 12:32:44 +0200 Subject: [PATCH 08/14] Add docs, fix linting Signed-off-by: SnowyLeopard --- docs/user/assets/settings-browserless.png | Bin 0 -> 16072 bytes docs/user/index.md | 14 +++++ .../ConfigImplementationTest.php | 48 +++++++++++++++--- .../Unit/Service/HtmlDownloadServiceTest.php | 4 +- 4 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 docs/user/assets/settings-browserless.png diff --git a/docs/user/assets/settings-browserless.png b/docs/user/assets/settings-browserless.png new file mode 100644 index 0000000000000000000000000000000000000000..96bbb305ca8b0e5c57516f20bdefccf22c4fef5f GIT binary patch literal 16072 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

      MXm5~kq`rXQI2?56L<0WNbhHXhZNCq! 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 + +You can create an account at https://www.browserless.io/ or setup a self-hosted instance. + +For the url be sure to supply the REST API url, see https://docs.browserless.io/overview/connection-urls. +For the Amsterdam region this would be: https://production-ams.browserless.io \ No newline at end of file diff --git a/tests/Unit/Controller/Implementation/ConfigImplementationTest.php b/tests/Unit/Controller/Implementation/ConfigImplementationTest.php index b7b245d40..c376964d3 100644 --- a/tests/Unit/Controller/Implementation/ConfigImplementationTest.php +++ b/tests/Unit/Controller/Implementation/ConfigImplementationTest.php @@ -112,6 +112,7 @@ public function testList(): void { * @param mixed $interval * @param mixed $printImage * @param mixed $visibleInfoBlocks + * @param mixed $browserlessConfig */ public function testConfig($data, $folderPath, $interval, $printImage, $visibleInfoBlocks, $browserlessConfig): void { $this->restParser->method('getParameters')->willReturn($data); @@ -161,24 +162,52 @@ public function testConfig($data, $folderPath, $interval, $printImage, $visibleI public static function dataProviderConfig() { return [ 'noChange' => [ - [], null, null, null, null, null + [], + null, + null, + null, + null, + null ], 'changeFolder' => [ - ['folder' => '/path/to/whatever'], '/path/to/whatever', null, null, null, null + ['folder' => '/path/to/whatever'], + '/path/to/whatever', + null, + null, + null, + null ], 'changeinterval' => [ - ['update_interval' => 15], null, 15, null, null, null + ['update_interval' => 15], + null, + 15, + null, + null, + null ], 'changePrint' => [ - ['print_image' => true], null, null, true, null, null + ['print_image' => true], + null, + null, + true, + null, + null ], 'changeVisibleBlocks' => [ ['visibleInfoBlocks' => ['cooking-time' => true, 'preparation-time' => true]], - null, null, null, ['cooking-time' => true, 'preparation-time' => true], null + null, + null, + null, + ['cooking-time' => true, 'preparation-time' => true], + null ], 'browserlessConfig' => [ ['browserless_config' => ['url' => 'https://something.com', 'token' => '123456789']], - null, null, null, null, ['url' => 'https://something.com', 'token' => '123456789'] + null, + null, + null, + null, + ['url' => 'https://something.com', 'token' => '123456789'] ], 'changeAll' => [ [ @@ -187,7 +216,12 @@ public static function dataProviderConfig() { 'print_image' => false, 'visibleInfoBlocks' => ['cooking-time' => true, 'preparation-time' => true], 'browserless_config' => ['url' => 'https://something.com', 'token' => '123456789'] - ], '/my/custom/path', 12, false, ['cooking-time' => true, 'preparation-time' => true], ['url' => 'https://something.com', 'token' => '123456789'] + ], + '/my/custom/path', + 12, + false, + ['cooking-time' => true, 'preparation-time' => true], + ['url' => 'https://something.com', 'token' => '123456789'] ], ]; } diff --git a/tests/Unit/Service/HtmlDownloadServiceTest.php b/tests/Unit/Service/HtmlDownloadServiceTest.php index f7003a5ba..34bde2073 100644 --- a/tests/Unit/Service/HtmlDownloadServiceTest.php +++ b/tests/Unit/Service/HtmlDownloadServiceTest.php @@ -202,14 +202,14 @@ public function testDownloadWithBrowserless() { $state = 12345; $contentType = 'The content type'; $encoding = 'utf-8'; - $browserlessUrl = "http://browserless.url/chromium/content?token=token"; + $browserlessUrl = 'http://browserless.url/chromium/content?token=token'; $this->downloadHelper->expects($this->once()) ->method('downloadFile')->with($browserlessUrl, $this->anything(), $this->anything()); $this->il10n->method('getLocaleCode')->willReturn('en-US'); $this->userConfigHelper->method('getBrowserlessConfig')->willReturn([ - 'url' => "http://browserless.url", + 'url' => 'http://browserless.url', 'token' => 'token', ]); From 1ef965f39fd7c731b7256eb9ce9c80f4a4a8d641 Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 25 May 2025 12:36:35 +0200 Subject: [PATCH 09/14] Another lint fix Signed-off-by: SnowyLeopard --- tests/Unit/Service/HtmlDownloadServiceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/Service/HtmlDownloadServiceTest.php b/tests/Unit/Service/HtmlDownloadServiceTest.php index 34bde2073..e63157c3d 100644 --- a/tests/Unit/Service/HtmlDownloadServiceTest.php +++ b/tests/Unit/Service/HtmlDownloadServiceTest.php @@ -8,11 +8,11 @@ use OCA\Cookbook\Exception\NoDownloadWasCarriedOutException; use OCA\Cookbook\Helper\DownloadEncodingHelper; use OCA\Cookbook\Helper\DownloadHelper; -use OCA\Cookbook\Helper\UserConfigHelper; use OCA\Cookbook\Helper\EncodingGuessingHelper; use OCA\Cookbook\Helper\HTMLFilter\HtmlEncodingFilter; use OCA\Cookbook\Helper\HTMLFilter\HtmlEntityDecodeFilter; use OCA\Cookbook\Helper\HtmlToDomParser; +use OCA\Cookbook\Helper\UserConfigHelper; use OCA\Cookbook\Service\HtmlDownloadService; use OCP\IL10N; use PHPUnit\Framework\MockObject\MockObject; From 73037ccc36aac0a6916f738af8bca25fd3d9a9b7 Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 25 May 2025 12:43:47 +0200 Subject: [PATCH 10/14] Fix types and linting Signed-off-by: SnowyLeopard --- lib/Helper/UserConfigHelper.php | 2 +- lib/Service/RecipeService.php | 2 +- src/components/Modals/SettingsDialog.vue | 36 ++++++++++++++++++------ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/lib/Helper/UserConfigHelper.php b/lib/Helper/UserConfigHelper.php index 6eee0479d..6a4c10bbf 100644 --- a/lib/Helper/UserConfigHelper.php +++ b/lib/Helper/UserConfigHelper.php @@ -231,7 +231,7 @@ public function setFolderName(string $value): void { /** * Gets the browserless config from the configuration * - * @return array keys: url and token, values: url and token + * @return array keys: url and token, values: url and token * @throws UserNotLoggedInException if no user is logged in */ public function getBrowserlessConfig(): array { diff --git a/lib/Service/RecipeService.php b/lib/Service/RecipeService.php index e476f11d5..ea9f90a9c 100755 --- a/lib/Service/RecipeService.php +++ b/lib/Service/RecipeService.php @@ -565,7 +565,7 @@ public function getVisibleInfoBlocks(): array { /** * Get browserless configuration - * @return array keys: url and token, values: url and token + * @return array keys: url and token, values: url and token */ public function getBrowserlessConfig(): array { return $this->userConfigHelper->getBrowserlessConfig(); diff --git a/src/components/Modals/SettingsDialog.vue b/src/components/Modals/SettingsDialog.vue index 7dbee0083..e81623e6d 100644 --- a/src/components/Modals/SettingsDialog.vue +++ b/src/components/Modals/SettingsDialog.vue @@ -165,21 +165,35 @@

      • - +
      • - +
      @@ -427,7 +441,10 @@ watch( return; } try { - await api.config.browserlessConfig.update({'url': newVal, 'token': browserlessToken.value}); + await api.config.browserlessConfig.update({ + url: newVal, + token: browserlessToken.value, + }); await store.dispatch('refreshConfig'); } catch { await showSimpleAlertModal( @@ -435,7 +452,7 @@ watch( ); browserlessUrl.value = oldVal; // Revert if save fails } - } + }, ); watch( @@ -445,7 +462,10 @@ watch( return; } try { - await api.config.browserlessConfig.update({'token': newVal, 'url': browserlessUrl.value}); + await api.config.browserlessConfig.update({ + token: newVal, + url: browserlessUrl.value, + }); await store.dispatch('refreshConfig'); } catch { await showSimpleAlertModal( @@ -453,7 +473,7 @@ watch( ); browserlessToken.value = oldVal; // Revert if save fails } - } + }, ); /** From ed1f606a78f57c2b5f39e9fb2faac28c4780aa6a Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 25 May 2025 12:54:16 +0200 Subject: [PATCH 11/14] Cleanup comments, remove debugging code Signed-off-by: SnowyLeopard --- lib/Helper/DownloadHelper.php | 5 ----- lib/Service/HtmlDownloadService.php | 8 ++++---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/Helper/DownloadHelper.php b/lib/Helper/DownloadHelper.php index 353d7dc3a..2e9b99bc8 100644 --- a/lib/Helper/DownloadHelper.php +++ b/lib/Helper/DownloadHelper.php @@ -74,11 +74,6 @@ public function downloadFile(string $url, array $options = [], array $headers = curl_setopt_array($ch, $options); $ret = curl_exec($ch); - $err = curl_error($ch); - - if ($err) { - echo 'cURL Error #:' . $err; - } if ($ret === false) { $ex = new NoDownloadWasCarriedOutException($this->l->t('Downloading of a file failed returned the following error message: %s', [curl_error($ch)])); diff --git a/lib/Service/HtmlDownloadService.php b/lib/Service/HtmlDownloadService.php index d6017d90f..1b00aab86 100644 --- a/lib/Service/HtmlDownloadService.php +++ b/lib/Service/HtmlDownloadService.php @@ -90,9 +90,9 @@ public function __construct( public function downloadRecipe(string $url): int { $browserlessConfig = $this->userConfigHelper->getBrowserlessConfig(); - // Check if a browserless url is available + // Check if a browserless configuration is available if (!empty($browserlessConfig['url']) && !empty($browserlessConfig['token'])) { - // Use Browserless API if the url is set + // Use Browserless API if the url and token are set $html = $this->fetchHtmlPageUsingBrowserless($url); } else { // Otherwise, use the standard method @@ -146,8 +146,8 @@ private function fetchHtmlPageUsingBrowserless(string $url): string { throw new ImportException($this->l->t('Browserless token is not configured.')); } - // API endpoint for Browserless.io - $apiEndpoint = $browserlessAddress . '/chromium/content?token=' . $browserlessToken; // Use the dynamic address + // API endpoint for Browserless + $apiEndpoint = $browserlessAddress . '/chromium/content?token=' . $browserlessToken; $langCode = $this->l->getLocaleCode(); $langCode = str_replace('_', '-', $langCode); From cf0cf285fc16ecc2d7492c5b6ecfbf3ccc0017fb Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 25 May 2025 13:03:25 +0200 Subject: [PATCH 12/14] Update api spec Signed-off-by: SnowyLeopard --- docs/dev/api/0.1.2/objects.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/dev/api/0.1.2/objects.yaml b/docs/dev/api/0.1.2/objects.yaml index 24f1b1047..7703543c5 100644 --- a/docs/dev/api/0.1.2/objects.yaml +++ b/docs/dev/api/0.1.2/objects.yaml @@ -48,6 +48,18 @@ VisibleInfoBlocks: example: false description: Show the list of tools in the UI +BrowserlessConfig: + type: object + properties: + url: + type: string + example: http://localhost:3000 + nullable: true + token: + type: string + example: ABCD + nullable: true + Config: type: object description: An object describing the configuration of the web app @@ -66,6 +78,8 @@ Config: description: True, if the user wished to print the recipe images with the rest of the recipes visibleInfoBlocks: $ref: "#/VisibleInfoBlocks" + browserless_config: + $reg: "#/BrowserlessConfig" Error: type: object From 038f70e5fab9d426091b09024afa75e5ce3068ed Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 25 May 2025 13:05:57 +0200 Subject: [PATCH 13/14] Add changelog Signed-off-by: SnowyLeopard --- .changelog/current/2780-add-browserless-support.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/current/2780-add-browserless-support.md diff --git a/.changelog/current/2780-add-browserless-support.md b/.changelog/current/2780-add-browserless-support.md new file mode 100644 index 000000000..c920a806a --- /dev/null +++ b/.changelog/current/2780-add-browserless-support.md @@ -0,0 +1,3 @@ +# Added + +- Added support for browserless From 31005076eb2c4ce290a33042326222ec3214cfb4 Mon Sep 17 00:00:00 2001 From: SnowyLeopard Date: Sun, 17 Aug 2025 11:27:26 +0200 Subject: [PATCH 14/14] update changelog Signed-off-by: SnowyLeopard --- ...add-browserless-support.md => 2825-add-browserless-support.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changelog/current/{2780-add-browserless-support.md => 2825-add-browserless-support.md} (100%) diff --git a/.changelog/current/2780-add-browserless-support.md b/.changelog/current/2825-add-browserless-support.md similarity index 100% rename from .changelog/current/2780-add-browserless-support.md rename to .changelog/current/2825-add-browserless-support.md