diff --git a/CHANGELOG.md b/CHANGELOG.md index a90a5def3f..91a894ac6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ You can also check [on GitHub](https://github.com/nextcloud/news/releases), the ## [25.x.x] ### Changed - API add new field to Feed that indicates when the next update will be done "nextUpdateTime" (#2993) +- Change logic to update feed only if the nextUpdateTime has been reached (#2999) +- Add setting to disable the usage of nextUpdateTime (#2999) ### Fixed - `TypeError: this.$refs.actions.$refs.menuButton is undefined` when tabbing through feeds and folders diff --git a/appinfo/info.xml b/appinfo/info.xml index f1493b8c0d..e3b6369a80 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -21,7 +21,7 @@ Create a [feature request](https://github.com/nextcloud/news/discussions/new) Report a [feed issue](https://github.com/nextcloud/news/discussions/new) ]]> - 25.1.2 + 25.2.0 agpl Benjamin Brahmer Sean Molenaar diff --git a/docs/admin.md b/docs/admin.md index 827d4ac84d..5de73c6e8a 100644 --- a/docs/admin.md +++ b/docs/admin.md @@ -63,11 +63,6 @@ The update interval is used to determine when the next update of all feeds shoul By default, the value is set to 3600 seconds (1 hour) You can configure this interval as an administrator. The new value is only applied after the next run of the updater. -#### What is a good update interval? +Since News 25.2.0 News no longer will update all feeds instead it will make a individual decision based on when an update for the feed will make sense. Therefore this interval is now only to be understood as the check if an update should be done. -That depends on your individual needs. -Please keep in mind that the lower you set your update interval, the more traffic is generated. - -#### Can I set individual update intervals per feed/user? - -No, that is not possible. +This behavior can be disabled in the Settings although we generally do not recommend that. diff --git a/docs/user.md b/docs/user.md new file mode 100644 index 0000000000..a9ab974bed --- /dev/null +++ b/docs/user.md @@ -0,0 +1,14 @@ +# User + +Welcome to the User documentation of News. + +# TODO +This documentation is work in progress. + +# User interface + +explain the different options in the UI especially options for feeds and settings + +# Using News with Clients + +explain sync and link to clients page \ No newline at end of file diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 6c307a7820..0cd4e7fc97 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -67,6 +67,7 @@ class Application extends App implements IBootstrap 'useCronUpdates' => true, 'exploreUrl' => '', 'updateInterval' => 3600, + 'useNextUpdateTime' => true, ]; public function __construct(array $urlParams = []) diff --git a/lib/Db/Feed.php b/lib/Db/Feed.php index 229a31c063..fd48d0cb4d 100644 --- a/lib/Db/Feed.php +++ b/lib/Db/Feed.php @@ -663,10 +663,12 @@ public function setUserId(string $userId): Feed /** * @param int $nextUpdateTime */ - public function setNextUpdateTime(?int $nextUpdateTime): void + public function setNextUpdateTime(?int $nextUpdateTime): Feed { $this->nextUpdateTime = $nextUpdateTime; $this->markFieldUpdated('nextUpdateTime'); + + return $this; } public function toAPI(): array diff --git a/lib/Fetcher/FeedFetcher.php b/lib/Fetcher/FeedFetcher.php index 8af8070f00..b8e1ab81e4 100755 --- a/lib/Fetcher/FeedFetcher.php +++ b/lib/Fetcher/FeedFetcher.php @@ -146,6 +146,13 @@ public function fetch( ); $feed->setNextUpdateTime($resource->getNextUpdate()?->getTimestamp()); + $this->logger->debug( + 'Feed {url} was parsed and nextUpdateTime is {nextUpdateTime}', + [ + 'url' => $url, + 'nextUpdateTime' => $feed->getNextUpdateTime() + ] + ); if (!is_null($resource->getResponse()->getLastModified())) { $feed->setHttpLastModified($resource->getResponse()->getLastModified()->format(DateTime::RSS)); @@ -158,10 +165,11 @@ public function fetch( $feedName = $parsedFeed->getTitle(); $feedAuthor = $parsedFeed->getAuthor(); $this->logger->debug( - 'Feed {url} was modified since last fetch. #{count} items', + 'Feed {url} was modified since last fetch. #{count} items, nextUpdateTime is {nextUpdateTime}', [ 'url' => $url, 'count' => count($parsedFeed), + 'nextUpdateTime' => $feed->getNextUpdateTime(), ] ); diff --git a/lib/Service/FeedServiceV2.php b/lib/Service/FeedServiceV2.php index c729d6da4a..ad984188df 100644 --- a/lib/Service/FeedServiceV2.php +++ b/lib/Service/FeedServiceV2.php @@ -21,10 +21,12 @@ use OCA\News\Db\FeedMapperV2; use OCA\News\Fetcher\FeedFetcher; +use OCA\News\AppInfo\Application; use OCA\News\Service\Exceptions\ServiceConflictException; use OCA\News\Service\Exceptions\ServiceNotFoundException; use OCP\AppFramework\Db\Entity; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\IAppConfig; use OCA\News\Db\Feed; use OCA\News\Db\Item; @@ -61,6 +63,10 @@ class FeedServiceV2 extends Service * @var Explorer */ protected $explorer; + /** + * @var IAppConfig + */ + protected $config; /** * FeedService constructor. @@ -71,6 +77,7 @@ class FeedServiceV2 extends Service * @param Explorer $explorer Feed Explorer * @param HTMLPurifier $purifier HTML Purifier * @param LoggerInterface $logger Logger + * @param IAppConfig $config App config */ public function __construct( FeedMapperV2 $mapper, @@ -78,7 +85,8 @@ public function __construct( ItemServiceV2 $itemService, Explorer $explorer, HTMLPurifier $purifier, - LoggerInterface $logger + LoggerInterface $logger, + IAppConfig $config ) { parent::__construct($mapper, $logger); @@ -86,6 +94,7 @@ public function __construct( $this->itemService = $itemService; $this->explorer = $explorer; $this->purifier = $purifier; + $this->config = $config; } /** @@ -244,12 +253,13 @@ public function create( $feed->setFolderId($folderId) ->setUserId($userId) ->setHttpLastModified(null) + ->setNextUpdateTime(null) ->setArticlesPerUpdate(count($items)); if ($title !== null) { $feed->setTitle($title); } - + if ($feed->getTitle() === null) { $feed->setTitle(parse_url($feedUrl)['host']); } @@ -276,6 +286,28 @@ public function fetch(Entity $feed): Entity return $feed; } + // Check if the nextUpdateTime check should be used + $useNextUpdateTime = $this->config->getValueBool( + Application::NAME, + 'useNextUpdateTime', + Application::DEFAULT_SETTINGS['useNextUpdateTime'] + ); + + if ($useNextUpdateTime) { + $nextUpdateTime = $feed->getNextUpdateTime(); + $currentTime = time(); + $tolerance = 10 * 60; // 10 minutes tolerance + + if ($nextUpdateTime !== null && ($currentTime + $tolerance) < $nextUpdateTime) { + $this->logger->info('Feed update skipped. Next update time not reached.', [ + 'feedUrl' => $feed->getUrl(), + 'nextUpdateTime' => $nextUpdateTime, + 'currentTime' => $currentTime, + ]); + return $feed; + } + } + // for backwards compatibility it can be that the location is not set // yet, if so use the url $location = $feed->getLocation() ?? $feed->getUrl(); @@ -326,6 +358,16 @@ public function fetch(Entity $feed): Entity $feed->setHttpLastModified($fetchedFeed->getHttpLastModified()) ->setLocation($fetchedFeed->getLocation()); + // check if useNextUpdateTime is set by the admin + // if so update value with the timestamp + // otherwise set it to null to indicate to clients + // that they can not use that field for any info. + if ($useNextUpdateTime) { + $feed->setNextUpdateTime($fetchedFeed->getNextUpdateTime()); + } else { + $feed->setNextUpdateTime(null); + } + foreach (array_reverse($items) as &$item) { $item->setFeedId($feed->getId()) ->setBody($this->purifier->purify($item->getBody())); diff --git a/src/components/AdminSettings.vue b/src/components/AdminSettings.vue index e2ea671b96..3c4ebe9732 100644 --- a/src/components/AdminSettings.vue +++ b/src/components/AdminSettings.vue @@ -87,6 +87,17 @@ SPDX-Licence-Identifier: AGPL-3.0-or-later

{{ t("news", "Interval in seconds in which the feeds will be updated.") }}

+ +
+ + {{ t("news", "Use next update time for feed updates") }} + +
+

+ {{ t("news", "Enable this to use the calculated next update time for feed updates. Disable to update feeds based solely on the update interval.") }} +

@@ -140,6 +151,7 @@ export default { feedFetcherTimeout: loadState('news', 'feedFetcherTimeout'), exploreUrl: loadState('news', 'exploreUrl'), updateInterval: loadState('news', 'updateInterval'), + useNextUpdateTime: loadState('news', 'useNextUpdateTime') === '1', relativeTime: moment(lastCron * 1000).fromNow(), lastCron, } @@ -159,7 +171,7 @@ export default { key, }, ) - if (key === 'useCronUpdates' || key === 'purgeUnread') { + if (key === 'useCronUpdates' || key === 'purgeUnread' || key === 'useNextUpdateTime') { value = value ? '1' : '0' } try { diff --git a/tests/Unit/Service/FeedServiceTest.php b/tests/Unit/Service/FeedServiceTest.php index fa3c958bd7..3ccdb7100a 100644 --- a/tests/Unit/Service/FeedServiceTest.php +++ b/tests/Unit/Service/FeedServiceTest.php @@ -24,6 +24,7 @@ use OCA\News\Service\ItemServiceV2; use OCA\News\Utility\Time; use OCP\AppFramework\Db\DoesNotExistException; +use OCP\IAppConfig; use OCA\News\Db\Feed; use OCA\News\Db\Item; @@ -77,6 +78,11 @@ class FeedServiceTest extends TestCase */ private $explorer; + /** + * @var \PHPUnit\Framework\MockObject\MockObject|IAppConfig + */ + private $config; + private $response; protected function setUp(): void @@ -112,14 +118,19 @@ protected function setUp(): void ->getMockBuilder(\HTMLPurifier::class) ->disableOriginalConstructor() ->getMock(); - + $this->config = $this + ->getMockBuilder(IAppConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->class = new FeedServiceV2( $this->mapper, $this->fetcher, $this->itemService, $this->explorer, $this->purifier, - $this->logger + $this->logger, + $this->config ); $this->uid = 'jack'; } diff --git a/tests/api/feeds.bats b/tests/api/feeds.bats index add265e5bb..8a4de1d714 100644 --- a/tests/api/feeds.bats +++ b/tests/api/feeds.bats @@ -147,5 +147,6 @@ teardown() { # run is not working here. output=$(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} POST ${BASE_URLv1}/feeds url=$NC_FEED | jq '.feeds | .[0].nextUpdateTime') - assert_output '2071387335' -} \ No newline at end of file + # the field nextUpdateTime should be null here since the feed just got created in the DB an the items have not yet been fetched + assert_output 'null' +} diff --git a/tests/javascript/unit/components/AdminSettings.spec.ts b/tests/javascript/unit/components/AdminSettings.spec.ts index 5f0280b9ff..8d4db59c77 100644 --- a/tests/javascript/unit/components/AdminSettings.spec.ts +++ b/tests/javascript/unit/components/AdminSettings.spec.ts @@ -28,7 +28,7 @@ describe('AdminSettings.vue', () => { }) it('should initialize and fetch settings from state', () => { - expect(loadState).toBeCalledTimes(8) + expect(loadState).toBeCalledTimes(9) }) it('should send post with updated settings', async () => { diff --git a/tests/test_helper/php-feed-generator b/tests/test_helper/php-feed-generator index 99bcecc021..5323cbe365 160000 --- a/tests/test_helper/php-feed-generator +++ b/tests/test_helper/php-feed-generator @@ -1 +1 @@ -Subproject commit 99bcecc0215568f87c60c3558b4492eb25518d33 +Subproject commit 5323cbe365bcee19293ef39705afa256c1b3c1a9 diff --git a/tests/updater/update.bats b/tests/updater/update.bats index e55661bc10..5c17d97d0b 100644 --- a/tests/updater/update.bats +++ b/tests/updater/update.bats @@ -38,14 +38,17 @@ teardown() { for i in $FOLDER_IDS; do http --ignore-stdin -b -a ${user}:${APP_PASSWORD} DELETE ${BASE_URLv1}/folders/$i > /dev/null done + + # set useNextUpdateTime to true + ./occ config:app:set news useNextUpdateTime --value=true } @test "[$TESTSUITE] Test simple update" { # Create Feed FEEDID=$(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} POST ${BASE_URLv1}/feeds url=$TEST_FEED | grep -Po '"id":\K([0-9]+)') - + sleep 2 - + # Get Items ID_LIST1=($(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/items | grep -Po '"id":\K([0-9]+)' | tr '\n' ' ')) # Trigger Update @@ -59,47 +62,55 @@ teardown() { assert_equal "${ID_LIST1[*]}" "${ID_LIST2[*]}" } -@test "[$TESTSUITE] Test simple update with new content" { +@test "[$TESTSUITE] Test if nextUpdateTime is updated" { # Create Feed - FEEDID=$(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} POST ${BASE_URLv1}/feeds url=$TEST_FEED | grep -Po '"id":\K([0-9]+)') - + FEED=$(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} POST ${BASE_URLv1}/feeds url=$TEST_FEED) + + UpdateTime1=$(echo $FEED | jq '.feeds | .[0].nextUpdateTime') + FEEDID=$(echo $FEED | jq '.feeds | .[0].id') + + assert_equal ${UpdateTime1} null + sleep 2 - - # Get Items - ID_LIST1=($(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/items | grep -Po '"id":\K([0-9]+)' | tr '\n' ' ')) - php ${BATS_TEST_DIRNAME}/../test_helper/php-feed-generator/feed-generator.php -a 15 -s 9 -f ${BATS_TEST_DIRNAME}/../test_helper/feeds/test.xml + # Get the current time + current_time=$(date +%s) + + # Calculate the expected time range (+1 hour with some tolerance) + expected_time_min=$((current_time + 3600 - 60)) # 1 hour - 1 minute tolerance + expected_time_max=$((current_time + 3600 + 60)) # 1 hour + 1 minute tolerance + php ${BATS_TEST_DIRNAME}/../test_helper/php-feed-generator/feed-generator.php -a 15 -s 9 -f ${BATS_TEST_DIRNAME}/../test_helper/feeds/test.xml # Trigger Update http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/feeds/update userId=${user} feedId=$FEEDID - + sleep 2 - # Get Items again - ID_LIST2=($(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/items | grep -Po '"id":\K([0-9]+)' | tr '\n' ' ')) + UpdateTime2=$(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/feeds | jq '.feeds | .[0].nextUpdateTime') - output="${ID_LIST2[*]}" + # Assert that UpdateTime2 is within the expected range + run bash -c "[[ $UpdateTime2 -ge $expected_time_min && $UpdateTime2 -le $expected_time_max ]]" + assert_success - # Check that they are not equal but that they match partially. - assert_not_equal "${ID_LIST1[*]}" "${ID_LIST2[*]}" - assert_output --partial "${ID_LIST1[*]}" } -@test "[$TESTSUITE] Test feed with 'outdated' items https://github.com/nextcloud/news/issues/2236 " { - # Create Feed, for the first fetch a timestamp today -1 year is used. +@test "[$TESTSUITE] Test simple update with new content" { + # Disable useNextUpdateTime + ./occ config:app:set news useNextUpdateTime --value=false + + # Create Feed FEEDID=$(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} POST ${BASE_URLv1}/feeds url=$TEST_FEED | grep -Po '"id":\K([0-9]+)') - + sleep 2 - + # Get Items ID_LIST1=($(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/items | grep -Po '"id":\K([0-9]+)' | tr '\n' ' ')) - # Generate Feed with older items (-o yes) - php ${BATS_TEST_DIRNAME}/../test_helper/php-feed-generator/feed-generator.php -a 15 -s 9 -f ${BATS_TEST_DIRNAME}/../test_helper/feeds/test.xml -o yes + php ${BATS_TEST_DIRNAME}/../test_helper/php-feed-generator/feed-generator.php -a 15 -s 9 -f ${BATS_TEST_DIRNAME}/../test_helper/feeds/test.xml # Trigger Update http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/feeds/update userId=${user} feedId=$FEEDID - + sleep 2 # Get Items again @@ -112,7 +123,38 @@ teardown() { assert_output --partial "${ID_LIST1[*]}" } +# older date is not a thing anymore +#@test "[$TESTSUITE] Test feed with 'outdated' items https://github.com/nextcloud/news/issues/2236 " { +# # Create Feed, for the first fetch a timestamp today -1 year is used. +# FEEDID=$(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} POST ${BASE_URLv1}/feeds url=$TEST_FEED | grep -Po '"id":\K([0-9]+)') +# +# sleep 2 +# +# # Get Items +# ID_LIST1=($(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/items | grep -Po '"id":\K([0-9]+)' | tr '\n' ' ')) +# +# # Generate Feed with older items (-o yes) +# php ${BATS_TEST_DIRNAME}/../test_helper/php-feed-generator/feed-generator.php -a 15 -s 9 -f ${BATS_TEST_DIRNAME}/../test_helper/feeds/test.xml -o yes +# +# # Trigger Update +# http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/feeds/update userId=${user} feedId=$FEEDID +# +# sleep 2 +# +# # Get Items again +# ID_LIST2=($(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/items | grep -Po '"id":\K([0-9]+)' | tr '\n' ' ')) +# +# output="${ID_LIST2[*]}" +# +# # Check that they are not equal but that they match partially. +# assert_not_equal "${ID_LIST1[*]}" "${ID_LIST2[*]}" +# assert_output --partial "${ID_LIST1[*]}" +#} + @test "[$TESTSUITE] Test purge with small feed" { + # Disable useNextUpdateTime + ./occ config:app:set news useNextUpdateTime --value=false + # Generate Feed with 210 items. php ${BATS_TEST_DIRNAME}/../test_helper/php-feed-generator/feed-generator.php -a 50 -s 0 -f ${BATS_TEST_DIRNAME}/../test_helper/feeds/test.xml # Create Feed @@ -152,10 +194,10 @@ teardown() { for n in "${ID_LIST[@]}" ; do ((n > max)) && max=$n done - + # mark all items of feed as read, returns nothing STATUS_CODE=$(http --ignore-stdin -hdo /tmp/body -a ${user}:${APP_PASSWORD} PUT ${BASE_URLv1}/feeds/$FEEDID/read newestItemId="$max" 2>&1| grep -Po '(?<=HTTP\/1\.1 )[0-9]{3}(?= OK)') - + # cleanup, purge items http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/cleanup/after-update @@ -174,13 +216,16 @@ teardown() { } @test "[$TESTSUITE] Test purge with more items than default limit 200" { + # Disable useNextUpdateTime + ./occ config:app:set news useNextUpdateTime --value=false + # Generate Feed with 210 items. php ${BATS_TEST_DIRNAME}/../test_helper/php-feed-generator/feed-generator.php -a 210 -f ${BATS_TEST_DIRNAME}/../test_helper/feeds/test.xml # Create Feed FEEDID=$(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} POST ${BASE_URLv1}/feeds url=$TEST_FEED | grep -Po '"id":\K([0-9]+)') - + sleep 2 - + # Get Items ID_LIST=($(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/items | grep -Po '"id":\K([0-9]+)' | tr '\n' ' ')) @@ -189,10 +234,10 @@ teardown() { for n in "${ID_LIST[@]}" ; do ((n > max)) && max=$n done - + # mark all items of feed as read, returns nothing STATUS_CODE=$(http --ignore-stdin -hdo /tmp/body -a ${user}:${APP_PASSWORD} PUT ${BASE_URLv1}/feeds/$FEEDID/read newestItemId="$max" 2>&1| grep -Po '(?<=HTTP\/1\.1 )[0-9]{3}(?= OK)') - + # cleanup, purge items http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/cleanup/after-update @@ -209,6 +254,9 @@ teardown() { } @test "[$TESTSUITE] Test Update and pruge with feed item>200; items<200" { + # Disable useNextUpdateTime + ./occ config:app:set news useNextUpdateTime --value=false + # Generate Feed with 210 items. php ${BATS_TEST_DIRNAME}/../test_helper/php-feed-generator/feed-generator.php -a 210 -f ${BATS_TEST_DIRNAME}/../test_helper/feeds/test.xml # Create Feed @@ -221,7 +269,7 @@ teardown() { for n in "${ID_LIST[@]}" ; do ((n > max)) && max=$n done - + # mark all items of feed as read, returns nothing STATUS_CODE=$(http --ignore-stdin -hdo /tmp/body -a ${user}:${APP_PASSWORD} PUT ${BASE_URLv1}/feeds/$FEEDID/read newestItemId="$max" 2>&1| grep -Po '(?<=HTTP\/1\.1 )[0-9]{3}(?= OK)') # cleanup, purge items @@ -243,7 +291,7 @@ teardown() { for n in "${ID_LIST[@]}" ; do ((n > max)) && max=$n done - + # mark all items of feed as read, returns nothing STATUS_CODE=$(http --ignore-stdin -hdo /tmp/body -a ${user}:${APP_PASSWORD} PUT ${BASE_URLv1}/feeds/$FEEDID/read newestItemId="$max" 2>&1| grep -Po '(?<=HTTP\/1\.1 )[0-9]{3}(?= OK)') @@ -262,11 +310,14 @@ teardown() { } @test "[$TESTSUITE] Test purge with two feeds with different item count limit" { + # Disable useNextUpdateTime + ./occ config:app:set news useNextUpdateTime --value=false + # Generate Feed with 260 items. php ${BATS_TEST_DIRNAME}/../test_helper/php-feed-generator/feed-generator.php -a 260 -f ${BATS_TEST_DIRNAME}/../test_helper/feeds/feed1.xml # Generate Feed with 210 items. php ${BATS_TEST_DIRNAME}/../test_helper/php-feed-generator/feed-generator.php -a 210 -f ${BATS_TEST_DIRNAME}/../test_helper/feeds/feed2.xml - + # Create Feeds FEED1ID=$(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} POST ${BASE_URLv1}/feeds url=$FEED1 | grep -Po '"id":\K([0-9]+)') FEED2ID=$(http --ignore-stdin -b -a ${user}:${APP_PASSWORD} POST ${BASE_URLv1}/feeds url=$FEED2 | grep -Po '"id":\K([0-9]+)') @@ -278,11 +329,11 @@ teardown() { for n in "${ID_LIST[@]}" ; do ((n > max)) && max=$n done - + # mark all items of both feeds as read, returns nothing STATUS_CODE1=$(http --ignore-stdin -hdo /tmp/body -a ${user}:${APP_PASSWORD} PUT ${BASE_URLv1}/feeds/$FEED1ID/read newestItemId="$max" 2>&1| grep -Po '(?<=HTTP\/1\.1 )[0-9]{3}(?= OK)') STATUS_CODE2=$(http --ignore-stdin -hdo /tmp/body -a ${user}:${APP_PASSWORD} PUT ${BASE_URLv1}/feeds/$FEED2ID/read newestItemId="$max" 2>&1| grep -Po '(?<=HTTP\/1\.1 )[0-9]{3}(?= OK)') - + # cleanup, purge items http --ignore-stdin -b -a ${user}:${APP_PASSWORD} GET ${BASE_URLv1}/cleanup/after-update