diff --git a/lib/Controller/Implementation/ConfigImplementation.php b/lib/Controller/Implementation/ConfigImplementation.php
index 49a0a2f63..8e995543a 100644
--- a/lib/Controller/Implementation/ConfigImplementation.php
+++ b/lib/Controller/Implementation/ConfigImplementation.php
@@ -32,6 +32,7 @@ public function __construct(
}
protected const KEY_VISIBLE_INFO_BLOCKS = 'visibleInfoBlocks';
+ protected const KEY_BROWSERLESS_ADDRESS = 'browserless_address';
/**
* Get the current configuration of the app
@@ -46,6 +47,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);
}
@@ -75,6 +77,10 @@ public function config() {
$this->service->setPrintImage((bool)$data['print_image']);
}
+ if (isset($data['browserlessAddress'])) {
+ $this->service->setBrowserlessAddress($data['browserlessAddress']);
+ }
+
if (isset($data[self::KEY_VISIBLE_INFO_BLOCKS])) {
$this->service->setVisibleInfoBlocks($data[self::KEY_VISIBLE_INFO_BLOCKS]);
}
diff --git a/lib/Helper/Filter/JSON/RecipeIdTypeFilter.php b/lib/Helper/Filter/JSON/RecipeIdTypeFilter.php
index 14acfa2ea..850619cfe 100644
--- a/lib/Helper/Filter/JSON/RecipeIdTypeFilter.php
+++ b/lib/Helper/Filter/JSON/RecipeIdTypeFilter.php
@@ -8,11 +8,15 @@
* The id should be a string and no integer.
*/
class RecipeIdTypeFilter extends AbstractJSONFilter {
- public function apply(array &$json): bool {
- $copy = $json;
- if (array_key_exists('id', $json)) {
- $json['id'] = strval($json['id']);
- }
+ public function apply(array &$json): bool {
+ $copy = $json;
+ if (array_key_exists('id', $json)) {
+ $json['id'] = strval($json['id']);
+ }
+ // Check and fix `id` under `author`
+ if (array_key_exists('author', $json) && is_array($json['author']) && array_key_exists('id', $json['author'])) {
+ $json['id'] = strval($json['author']['id']);
+ }
return $json !== $copy;
}
diff --git a/lib/Helper/UserConfigHelper.php b/lib/Helper/UserConfigHelper.php
index f78cc5f62..0333803fe 100644
--- a/lib/Helper/UserConfigHelper.php
+++ b/lib/Helper/UserConfigHelper.php
@@ -41,6 +41,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';
/**
* Checks if the user is logged in and the configuration can be obtained at all
@@ -155,6 +156,27 @@ public function setPrintImage(bool $value): void {
$this->setRawValue(self::KEY_PRINT_IMAGE, '0');
}
}
+ /**
+ * 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);
+ }
/**
* Determines which info blocks are displayed next to the recipe
diff --git a/lib/Service/HtmlDownloadService.php b/lib/Service/HtmlDownloadService.php
index 5ce2013ef..7fa5d8caa 100644
--- a/lib/Service/HtmlDownloadService.php
+++ b/lib/Service/HtmlDownloadService.php
@@ -13,6 +13,7 @@
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;
@@ -49,7 +50,11 @@ class HtmlDownloadService {
*/
private $dom;
+ /** @var userConfigHelper */
+ private $userConfigHelper;
+
public function __construct(
+ userConfigHelper $userConfigHelper,
HtmlEntityDecodeFilter $htmlEntityDecodeFilter,
HtmlEncodingFilter $htmlEncodingFilter,
IL10N $l10n,
@@ -59,6 +64,7 @@ public function __construct(
EncodingGuessingHelper $encodingGuesser,
DownloadEncodingHelper $downloadEncodingHelper,
) {
+ $this->userConfigHelper = $userConfigHelper;
$this->htmlFilters = [
$htmlEntityDecodeFilter,
$htmlEncodingFilter,
@@ -82,7 +88,16 @@ public function __construct(
* @throws ImportException If obtaining of the URL was not possible
*/
public function downloadRecipe(string $url): int {
- $html = $this->fetchHtmlPage($url);
+ $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 */
@@ -113,7 +128,61 @@ public function getDom(): ?DOMDocument {
*
* @return string The content of the page as a plain string
*/
- private function fetchHtmlPage(string $url): string {
+ /**
+ * Fetch an HTML page from Browserless.io (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(); // Retrieve from userConfigHelper or wherever it's stored
+
+ 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 . '/content'; // Use the dynamic address
+ // Prepare the data to be sent in the POST request
+ $data = json_encode([
+ 'url' => $url,
+ ]);
+
+ // Define the HTTP context options for the request
+ $options = [
+ 'http' => [
+ 'method' => 'POST',
+ 'header' => "Content-Type: application/json\r\n",
+ 'content' => $data,
+ ],
+ ];
+
+ // Create the stream context for the request
+ $context = stream_context_create($options);
+
+ // Send the request to Browserless.io
+ $response = file_get_contents($apiEndpoint, false, $context);
+
+ // Check if the response was successful
+ if ($response === false) {
+ $this->logger->error('Failed to fetch rendered HTML from Browserless.io');
+ throw new ImportException($this->l->t('Failed to fetch rendered HTML.'));
+ }
+
+ // Create a new DOMDocument to parse the HTML response
+
+ // You can return the DOMDocument or the raw HTML string based on your needs
+ return $response;
+ }
+
+ 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 b7c0cf7cc..fb82355e1 100755
--- a/lib/Service/RecipeService.php
+++ b/lib/Service/RecipeService.php
@@ -564,6 +564,14 @@ public function getVisibleInfoBlocks(): array {
return $this->userConfigHelper->getVisibleInfoBlocks();
}
+ public function getBrowserlessAddress() {
+ return $this->userConfigHelper->getBrowserlessAddress(); // Default to an empty string if not set
+ }
+
+ public function setBrowserlessAddress(string $address) {
+ $this->userConfigHelper->setBrowserlessAddress($address);
+ }
+
/**
* Get recipe file contents as an array
*
diff --git a/src/components/Modals/SettingsDialog.vue b/src/components/Modals/SettingsDialog.vue
index 7d96821d0..78cfcff11 100644
--- a/src/components/Modals/SettingsDialog.vue
+++ b/src/components/Modals/SettingsDialog.vue
@@ -46,6 +46,25 @@
+
+
+ }
*/
const writeChanges = ref(true);
+/**
+ * @type {import('vue').Ref}
+ */
+const browserlessAddress = ref('');
// Watchers
watch(
@@ -383,6 +406,23 @@ 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
@@ -434,6 +474,7 @@ const handleShowSettings = () => {
showFiltersInRecipeList.value =
store.state.localSettings.showFiltersInRecipeList;
updateInterval.value = config.update_interval;
+ browserlessAddress.value = config.browserlessAddress;
recipeFolder.value = config.folder;
nextTick(() => {
diff --git a/src/js/api-interface.js b/src/js/api-interface.js
index 9150bd61f..0f0cece3c 100644
--- a/src/js/api-interface.js
+++ b/src/js/api-interface.js
@@ -112,6 +112,10 @@ function reindex() {
return instance.post(`${baseUrl}/reindex`);
}
+function updateBrowserlessAddress(newAddress) {
+ return instance.post(`${baseUrl}/config`, { browserlessAddress: newAddress });
+}
+
export default {
recipes: {
create: createNewRecipe,
@@ -146,5 +150,8 @@ export default {
visibleInfoBlocks: {
update: updateVisibleInfoBlocks,
},
+ browserlessAddress: {
+ update: updateBrowserlessAddress,
+ },
},
};