From 248beb3442fa25285422d197087303e316a4cf4d Mon Sep 17 00:00:00 2001 From: Rodrigo Braz Date: Thu, 4 Apr 2024 22:19:10 +0100 Subject: [PATCH 1/9] feat(justwatch): Add support for justwatch --- app/modules/justwatch.py | 42 ++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 app/modules/justwatch.py diff --git a/app/modules/justwatch.py b/app/modules/justwatch.py new file mode 100644 index 0000000..18587e9 --- /dev/null +++ b/app/modules/justwatch.py @@ -0,0 +1,42 @@ +from simplejustwatchapi.justwatch import search + +from app import logger + + +class JustWatch: + def __init__(self, country, language): + self.country = country + self.language = language + + """ + Search for a title on JustWatch API + Returns: + [MediaEntry(entry_id='ts8', object_id=8, object_type='SHOW', title='Better Call Saul', url='https://justwatch.com/pt/serie/better-call-saul', release_year=2015, release_date='2015-02-08', runtime_minutes=50, short_description='Six years before Saul Goodman meets Walter White. We meet him when the man who will become Saul Goodman is known as Jimmy McGill, a small-time lawyer searching for his destiny, and, more immediately, hustling to make ends meet. Working alongside, and, often, against Jimmy, is “fixer” Mike Ehrmantraut. The series tracks Jimmy’s transformation into Saul Goodman, the man who puts “criminal” in “criminal lawyer".', genres=['crm', 'drm'], imdb_id='tt3032476', poster='https://images.justwatch.com/poster/269897858/s718/better-call-saul.jpg', backdrops=['https://images.justwatch.com/backdrop/171468199/s1920/better-call-saul.jpg', 'https://images.justwatch.com/backdrop/269897860/s1920/better-call-saul.jpg', 'https://images.justwatch.com/backdrop/302946702/s1920/better-call-saul.jpg', 'https://images.justwatch.com/backdrop/304447863/s1920/better-call-saul.jpg', 'https://images.justwatch.com/backdrop/273394969/s1920/better-call-saul.jpg'], offers=[Offer(id='b2Z8dHM4OlBUOjg6ZmxhdHJhdGU6NGs=', monetization_type='FLATRATE', presentation_type='_4K', price_string=None, price_value=None, price_currency='EUR', last_change_retail_price_value=None, type='AGGREGATED', package=OfferPackage(id='cGF8OA==', package_id=8, name='Netflix', technical_name='netflix', icon='https://images.justwatch.com/icon/207360008/s100/netflix.png'), url='http://www.netflix.com/title/80021955', element_count=6, available_to=None, deeplink_roku='launch/12?contentID=80021955&MediaType=show', subtitle_languages=[], video_technology=[], audio_technology=[], audio_languages=[])])] + """ + + def _search(self, title, max_results=5, detailed=True): + return search(title, self.country, self.language, max_results, detailed) + + def search_by_title_and_year(self, title, year, media_type): + results = self.search(title) + return [ + entry + for entry in results + if entry.title == title and entry.release_year == year + ] + + def available_on(self, title, year, media_type, providers): + result = self.search_by_title_and_year(title, year, media_type) + if not result: + logger.debug("No results found for title: {title}") + return False + + for provider in providers: + if provider in result.offers or "any" in result.offers: + logger.debug("Title {title} available on provider:{provider}") + return True + + return False + + def is_not_available_on(self, title, year, media_type, providers): + return not self.available_on(title, year, media_type, providers) diff --git a/requirements.txt b/requirements.txt index 214a51b..7322e90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,4 @@ PyYAML==6.0.1 requests==2.31.0 tautulli==3.7.0.2120 trakt.py==4.4.0 -pytest-mock \ No newline at end of file +simple-justwatch-python-api==0.14 \ No newline at end of file From d5dc137623581c38934e75702fa65352a48740b5 Mon Sep 17 00:00:00 2001 From: Rodrigo Braz Date: Fri, 5 Apr 2024 14:55:44 +0100 Subject: [PATCH 2/9] chore(cleanup): Remove unused code --- app/modules/tautulli.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/modules/tautulli.py b/app/modules/tautulli.py index 40dc531..68ee0b3 100644 --- a/app/modules/tautulli.py +++ b/app/modules/tautulli.py @@ -34,9 +34,6 @@ def __init__(self, url, api_key): def test_connection(self): self.api.status() - def get_last_episode_activity(self, library_config, section): - return self.get_activity(library_config, section) - def refresh_library(self, section_id): self.api.get_library_media_info(section_id=section_id, refresh=True) From 427d38b35c664e0f4569f5c7df7b78c31018363e Mon Sep 17 00:00:00 2001 From: Rodrigo Braz Date: Fri, 5 Apr 2024 15:37:11 +0100 Subject: [PATCH 3/9] feat(justwatch): Add justwatch module implementation --- app/modules/justwatch.py | 15 ++- tests/modules/test_justwatch.py | 174 ++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 tests/modules/test_justwatch.py diff --git a/app/modules/justwatch.py b/app/modules/justwatch.py index 18587e9..348cd92 100644 --- a/app/modules/justwatch.py +++ b/app/modules/justwatch.py @@ -7,6 +7,11 @@ class JustWatch: def __init__(self, country, language): self.country = country self.language = language + logger.debug( + "JustWatch instance created with country: %s and language: %s", + country, + language, + ) """ Search for a title on JustWatch API @@ -18,7 +23,7 @@ def _search(self, title, max_results=5, detailed=True): return search(title, self.country, self.language, max_results, detailed) def search_by_title_and_year(self, title, year, media_type): - results = self.search(title) + results = self._search(title) return [ entry for entry in results @@ -31,9 +36,13 @@ def available_on(self, title, year, media_type, providers): logger.debug("No results found for title: {title}") return False + if "any" in providers and result.offers: + logger.debug("Title {title} available on any provider") + return True + for provider in providers: - if provider in result.offers or "any" in result.offers: - logger.debug("Title {title} available on provider:{provider}") + if provider in result.offers: + logger.debug("Title {title} available on {provider}") return True return False diff --git a/tests/modules/test_justwatch.py b/tests/modules/test_justwatch.py new file mode 100644 index 0000000..4536d77 --- /dev/null +++ b/tests/modules/test_justwatch.py @@ -0,0 +1,174 @@ +import unittest +from unittest.mock import MagicMock, patch + +from simplejustwatchapi.query import MediaEntry + +from app.modules.justwatch import JustWatch + + +class TestJustWatch(unittest.TestCase): + @patch("app.modules.justwatch.search") + def test_search(self, mock_search): + # Arrange + mock_search.return_value = ["result1", "result2", "result3"] + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance._search("test_title") + + # Assert + mock_search.assert_called_once_with("test_title", "US", "en", 5, True) + self.assertEqual(result, ["result1", "result2", "result3"]) + + @patch.object(JustWatch, "_search") + def test_search_by_title_and_year(self, mock_search): + # Arrange + mock_entry1 = MagicMock() + mock_entry1.title = "title1" + mock_entry1.release_year = 2001 + + mock_entry2 = MagicMock() + mock_entry2.title = "title2" + mock_entry2.release_year = 2002 + + mock_entry3 = MagicMock() + mock_entry3.title = "title1" + mock_entry3.release_year = 2002 + + mock_search.return_value = [mock_entry1, mock_entry2, mock_entry3] + + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance.search_by_title_and_year("title1", 2001, "movie") + + # Assert + mock_search.assert_called_once_with("title1") + self.assertEqual( + result, + [mock_entry1], + ) + + @patch.object(JustWatch, "_search") + def test_search_by_title_and_year(self, mock_search): + # Arrange + mock_entry1 = MagicMock() + mock_entry1.title = "title1" + mock_entry1.release_year = 2001 + + mock_entry2 = MagicMock() + mock_entry2.title = "title2" + mock_entry2.release_year = 2002 + + mock_entry3 = MagicMock() + mock_entry3.title = "title1" + mock_entry3.release_year = 2001 + + mock_search.return_value = [mock_entry1, mock_entry2, mock_entry3] + + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance.search_by_title_and_year("title1", 2001, "movie") + + # Assert + mock_search.assert_called_once_with("title1") + self.assertEqual( + result, + [mock_entry1, mock_entry3], + ) + + @patch.object(JustWatch, "search_by_title_and_year") + def test_available_on(self, mock_search_by_title_and_year): + # Arrange + mock_entry1 = MagicMock() + mock_entry1.offers = ["provider1", "provider2"] + + mock_search_by_title_and_year.return_value = mock_entry1 + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance.available_on("title1", 2001, "movie", ["provider1"]) + + # Assert + mock_search_by_title_and_year.assert_called_once_with("title1", 2001, "movie") + self.assertTrue(result) + + @patch.object(JustWatch, "search_by_title_and_year") + def test_available_on_false(self, mock_search_by_title_and_year): + # Arrange + mock_entry1 = MagicMock() + mock_entry1.offers = ["provider2", "provider3"] + + mock_search_by_title_and_year.return_value = mock_entry1 + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance.available_on("title1", 2001, "movie", ["provider1"]) + + # Assert + mock_search_by_title_and_year.assert_called_once_with("title1", 2001, "movie") + self.assertFalse(result) + + @patch.object(JustWatch, "search_by_title_and_year") + def test_available_on_any(self, mock_search_by_title_and_year): + # Arrange + mock_entry1 = MagicMock() + mock_entry1.offers = ["provider1", "provider2"] + + mock_search_by_title_and_year.return_value = mock_entry1 + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance.available_on("title1", 2001, "movie", ["any"]) + + # Assert + mock_search_by_title_and_year.assert_called_once_with("title1", 2001, "movie") + self.assertTrue(result) + + @patch.object(JustWatch, "search_by_title_and_year") + def test_available_on_no_result(self, mock_search_by_title_and_year): + # Arrange + mock_search_by_title_and_year.return_value = None + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance.available_on("title1", 2001, "movie", ["provider1"]) + + # Assert + mock_search_by_title_and_year.assert_called_once_with("title1", 2001, "movie") + self.assertFalse(result) + + @patch.object(JustWatch, "available_on") + def test_is_not_available_on_true(self, mock_available_on): + # Arrange + mock_available_on.return_value = False + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance.is_not_available_on( + "title1", 2001, "movie", ["provider1"] + ) + + # Assert + mock_available_on.assert_called_once_with( + "title1", 2001, "movie", ["provider1"] + ) + self.assertTrue(result) + + @patch.object(JustWatch, "available_on") + def test_is_not_available_on_false(self, mock_available_on): + # Arrange + mock_available_on.return_value = True + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance.is_not_available_on( + "title1", 2001, "movie", ["provider1"] + ) + + # Assert + mock_available_on.assert_called_once_with( + "title1", 2001, "movie", ["provider1"] + ) + self.assertFalse(result) From 9cc1ebd5fc2b2af500608b1812981643e60a786b Mon Sep 17 00:00:00 2001 From: Rodrigo Braz Date: Fri, 5 Apr 2024 16:55:33 +0100 Subject: [PATCH 4/9] feat(justwatch): Add test coverage --- Makefile | 10 ++++ app/deleterr.py | 2 +- app/modules/justwatch.py | 18 +++---- tests/modules/test_justwatch.py | 93 +++++++++++++++++++++++++-------- tox.ini | 7 ++- 5 files changed, 98 insertions(+), 32 deletions(-) diff --git a/Makefile b/Makefile index 379ceaa..c4757e2 100644 --- a/Makefile +++ b/Makefile @@ -17,3 +17,13 @@ test: coverage run -m pytest coverage report coverage xml + +unit: + coverage run -m pytest -m "not integration" + coverage report + coverage xml + +integration: + coverage run -m pytest -m integration + coverage report + coverage xml \ No newline at end of file diff --git a/app/deleterr.py b/app/deleterr.py index ee1ca36..3ff6b60 100644 --- a/app/deleterr.py +++ b/app/deleterr.py @@ -99,8 +99,8 @@ def main(): default="/config/settings.yaml", help="Path to the config file", ) - args = parser.parse_args() + args, unknown = parser.parse_known_args() config = load_config(args.config) config.validate() diff --git a/app/modules/justwatch.py b/app/modules/justwatch.py index 348cd92..03f319d 100644 --- a/app/modules/justwatch.py +++ b/app/modules/justwatch.py @@ -19,16 +19,15 @@ def __init__(self, country, language): [MediaEntry(entry_id='ts8', object_id=8, object_type='SHOW', title='Better Call Saul', url='https://justwatch.com/pt/serie/better-call-saul', release_year=2015, release_date='2015-02-08', runtime_minutes=50, short_description='Six years before Saul Goodman meets Walter White. We meet him when the man who will become Saul Goodman is known as Jimmy McGill, a small-time lawyer searching for his destiny, and, more immediately, hustling to make ends meet. Working alongside, and, often, against Jimmy, is “fixer” Mike Ehrmantraut. The series tracks Jimmy’s transformation into Saul Goodman, the man who puts “criminal” in “criminal lawyer".', genres=['crm', 'drm'], imdb_id='tt3032476', poster='https://images.justwatch.com/poster/269897858/s718/better-call-saul.jpg', backdrops=['https://images.justwatch.com/backdrop/171468199/s1920/better-call-saul.jpg', 'https://images.justwatch.com/backdrop/269897860/s1920/better-call-saul.jpg', 'https://images.justwatch.com/backdrop/302946702/s1920/better-call-saul.jpg', 'https://images.justwatch.com/backdrop/304447863/s1920/better-call-saul.jpg', 'https://images.justwatch.com/backdrop/273394969/s1920/better-call-saul.jpg'], offers=[Offer(id='b2Z8dHM4OlBUOjg6ZmxhdHJhdGU6NGs=', monetization_type='FLATRATE', presentation_type='_4K', price_string=None, price_value=None, price_currency='EUR', last_change_retail_price_value=None, type='AGGREGATED', package=OfferPackage(id='cGF8OA==', package_id=8, name='Netflix', technical_name='netflix', icon='https://images.justwatch.com/icon/207360008/s100/netflix.png'), url='http://www.netflix.com/title/80021955', element_count=6, available_to=None, deeplink_roku='launch/12?contentID=80021955&MediaType=show', subtitle_languages=[], video_technology=[], audio_technology=[], audio_languages=[])])] """ - def _search(self, title, max_results=5, detailed=True): + def _search(self, title, max_results=5, detailed=False): return search(title, self.country, self.language, max_results, detailed) def search_by_title_and_year(self, title, year, media_type): results = self._search(title) - return [ - entry - for entry in results - if entry.title == title and entry.release_year == year - ] + for entry in results: + if entry.title == title and entry.release_year == year: + return entry + return None def available_on(self, title, year, media_type, providers): result = self.search_by_title_and_year(title, year, media_type) @@ -41,9 +40,10 @@ def available_on(self, title, year, media_type, providers): return True for provider in providers: - if provider in result.offers: - logger.debug("Title {title} available on {provider}") - return True + for offer in result.offers: + if offer.package.technical_name == provider.lower(): + logger.debug("Title {title} available on {provider}") + return True return False diff --git a/tests/modules/test_justwatch.py b/tests/modules/test_justwatch.py index 4536d77..6b19c27 100644 --- a/tests/modules/test_justwatch.py +++ b/tests/modules/test_justwatch.py @@ -1,12 +1,12 @@ -import unittest from unittest.mock import MagicMock, patch -from simplejustwatchapi.query import MediaEntry +import pytest from app.modules.justwatch import JustWatch -class TestJustWatch(unittest.TestCase): +@pytest.mark.unit +class TestJustWatch: @patch("app.modules.justwatch.search") def test_search(self, mock_search): # Arrange @@ -17,8 +17,8 @@ def test_search(self, mock_search): result = justwatch_instance._search("test_title") # Assert - mock_search.assert_called_once_with("test_title", "US", "en", 5, True) - self.assertEqual(result, ["result1", "result2", "result3"]) + mock_search.assert_called_once_with("test_title", "US", "en", 5, False) + assert result == ["result1", "result2", "result3"] @patch.object(JustWatch, "_search") def test_search_by_title_and_year(self, mock_search): @@ -44,10 +44,7 @@ def test_search_by_title_and_year(self, mock_search): # Assert mock_search.assert_called_once_with("title1") - self.assertEqual( - result, - [mock_entry1], - ) + assert result == mock_entry1 @patch.object(JustWatch, "_search") def test_search_by_title_and_year(self, mock_search): @@ -73,16 +70,16 @@ def test_search_by_title_and_year(self, mock_search): # Assert mock_search.assert_called_once_with("title1") - self.assertEqual( - result, - [mock_entry1, mock_entry3], - ) + assert result == mock_entry1 @patch.object(JustWatch, "search_by_title_and_year") def test_available_on(self, mock_search_by_title_and_year): # Arrange + mock_offer = MagicMock() + mock_offer.package.technical_name = "provider1" + mock_entry1 = MagicMock() - mock_entry1.offers = ["provider1", "provider2"] + mock_entry1.offers = [mock_offer] mock_search_by_title_and_year.return_value = mock_entry1 justwatch_instance = JustWatch("US", "en") @@ -92,13 +89,16 @@ def test_available_on(self, mock_search_by_title_and_year): # Assert mock_search_by_title_and_year.assert_called_once_with("title1", 2001, "movie") - self.assertTrue(result) + assert result @patch.object(JustWatch, "search_by_title_and_year") def test_available_on_false(self, mock_search_by_title_and_year): # Arrange + mock_offer = MagicMock() + mock_offer.package.technical_name = "provider2" + mock_entry1 = MagicMock() - mock_entry1.offers = ["provider2", "provider3"] + mock_entry1.offers = [mock_offer] mock_search_by_title_and_year.return_value = mock_entry1 justwatch_instance = JustWatch("US", "en") @@ -108,7 +108,7 @@ def test_available_on_false(self, mock_search_by_title_and_year): # Assert mock_search_by_title_and_year.assert_called_once_with("title1", 2001, "movie") - self.assertFalse(result) + assert not result @patch.object(JustWatch, "search_by_title_and_year") def test_available_on_any(self, mock_search_by_title_and_year): @@ -124,7 +124,7 @@ def test_available_on_any(self, mock_search_by_title_and_year): # Assert mock_search_by_title_and_year.assert_called_once_with("title1", 2001, "movie") - self.assertTrue(result) + assert result @patch.object(JustWatch, "search_by_title_and_year") def test_available_on_no_result(self, mock_search_by_title_and_year): @@ -137,7 +137,7 @@ def test_available_on_no_result(self, mock_search_by_title_and_year): # Assert mock_search_by_title_and_year.assert_called_once_with("title1", 2001, "movie") - self.assertFalse(result) + assert not result @patch.object(JustWatch, "available_on") def test_is_not_available_on_true(self, mock_available_on): @@ -154,7 +154,7 @@ def test_is_not_available_on_true(self, mock_available_on): mock_available_on.assert_called_once_with( "title1", 2001, "movie", ["provider1"] ) - self.assertTrue(result) + assert result @patch.object(JustWatch, "available_on") def test_is_not_available_on_false(self, mock_available_on): @@ -171,4 +171,55 @@ def test_is_not_available_on_false(self, mock_available_on): mock_available_on.assert_called_once_with( "title1", 2001, "movie", ["provider1"] ) - self.assertFalse(result) + assert not result + + +@pytest.mark.integration +class TestJustWatchIntegration: + def test_search(self): + # Arrange + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance._search("Better Call Saul") + + # Assert + assert isinstance(result, list) + + assert result + + def test_search_by_title_and_year(self): + # Arrange + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance.search_by_title_and_year( + "Better Call Saul", 2015, "show" + ) + + # Assert that the object exists + assert result + + def test_available_on(self): + # Arrange + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance.available_on( + "Better Call Saul", 2015, "show", ["Netflix"] + ) + + # Assert + assert result + + def test_is_not_available_on(self): + # Arrange + justwatch_instance = JustWatch("US", "en") + + # Act + result = justwatch_instance.is_not_available_on( + "Better Call Saul", 2015, "show", ["Disney+"] + ) + + # Assert + assert result diff --git a/tox.ini b/tox.ini index c52b511..3300288 100644 --- a/tox.ini +++ b/tox.ini @@ -2,4 +2,9 @@ max-line-length = 200 extend-ignore = E203 exclude = tests/* -max-complexity = 10 \ No newline at end of file +max-complexity = 10 + +[pytest] +markers = + unit: marks tests as unit tests + integration: marks tests as integration tests \ No newline at end of file From eb32545347deae40458b84a927847693a12ae643 Mon Sep 17 00:00:00 2001 From: Rodrigo Braz Date: Fri, 5 Apr 2024 17:29:05 +0100 Subject: [PATCH 5/9] chore(ci): Add integration tests step to CI --- .github/workflows/ci.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4347f54..633381e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ on: - "develop" jobs: - ci: + unit_tests: runs-on: ubuntu-latest permissions: # Gives the action the necessary permissions for publishing new @@ -40,7 +40,7 @@ jobs: pip install -r requirements.txt - name: Test with pytest run: | - coverage run -m pytest + coverage run -m pytest -m "not integration" coverage report coverage xml - name: SonarCloud Scan @@ -48,4 +48,23 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - + integration_tests: + needs: unit_tests + runs-on: ubuntu-latest + steps: + # Purges github badge cache + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python 3 + uses: actions/setup-python@v3 + with: + python-version: "3" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest + pip install -r requirements.txt + - name: Test with pytest + run: | + pytest -m integration \ No newline at end of file From 6d164a05931161a4e78c3c76444d3c16dbac29a7 Mon Sep 17 00:00:00 2001 From: Rodrigo Braz Date: Fri, 5 Apr 2024 17:41:32 +0100 Subject: [PATCH 6/9] chore(ci): Add missing pytest-mock --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 633381e..8505326 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pytest coverage + pip install pytest pytest-mock coverage pip install -r requirements.txt - name: Test with pytest run: | @@ -63,8 +63,8 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pytest + pip install pytest pytest-mock pip install -r requirements.txt - name: Test with pytest run: | - pytest -m integration \ No newline at end of file + python -m pytest -m integration \ No newline at end of file From 7a6a9c5ecb3dab1ae8834c4917a71af0b97be075 Mon Sep 17 00:00:00 2001 From: Rodrigo Braz Date: Sun, 7 Apr 2024 10:53:54 +0100 Subject: [PATCH 7/9] chore(justwatch): Add manual script to gather justwatch providers --- .gitignore | 5 ++++- app/deleterr.py | 11 ++++++++++ app/scripts/__init__.py | 0 app/scripts/justwatch_providers.py | 34 ++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 app/scripts/__init__.py create mode 100644 app/scripts/justwatch_providers.py diff --git a/.gitignore b/.gitignore index 868e66b..a68c0d3 100644 --- a/.gitignore +++ b/.gitignore @@ -190,4 +190,7 @@ pyrightconfig.json pyvenv.cfg pip-selfcheck.json -# End of https://www.toptal.com/developers/gitignore/api/python,virtualenv \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/python,virtualenv + +# Exception for app/deleterr/scripts +!/app/scripts/ \ No newline at end of file diff --git a/app/deleterr.py b/app/deleterr.py index 3ff6b60..bbc0700 100644 --- a/app/deleterr.py +++ b/app/deleterr.py @@ -99,8 +99,19 @@ def main(): default="/config/settings.yaml", help="Path to the config file", ) + parser.add_argument( + "--jw-providers", action="store_true", help="Gather JustWatch providers" + ) args, unknown = parser.parse_known_args() + + # If providers flag is set, gather JustWatch providers and exit + if args.jw_providers: + from app.scripts.justwatch_providers import gather_providers + + gather_providers() + return + config = load_config(args.config) config.validate() diff --git a/app/scripts/__init__.py b/app/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/scripts/justwatch_providers.py b/app/scripts/justwatch_providers.py new file mode 100644 index 0000000..155f310 --- /dev/null +++ b/app/scripts/justwatch_providers.py @@ -0,0 +1,34 @@ +from app.modules.justwatch import JustWatch + +SHOWS = [ + ("Loki", 2021), + ("Stranger Things", 2016), + ("Reacher", 2022), + ("Logan", 2017), + ("Severance", 2022), + ("Cobra Kai", 2018), + ("Bo Burnham: What", 2013), + ("Interstellar", 2014), + ("Eraserhead", 1977), + ("Life on Earth", 1979), + ("24", 2001), + ("Current Sea", 2020), +] + + +def gather_providers(): + # Create a JustWatch instance + justwatch = JustWatch("US", "en") + + # Create a set to store the providers + providers = set() + + # Iterate shows and collect all the different providers in a set + for title, year in SHOWS: + result = justwatch.search_by_title_and_year(title, year, "show") + if result: + for offer in result.offers: + providers.add(offer.package.technical_name) + + # Print the providers + print(providers) From 189313c28b673095293b21c316470da46b6f292a Mon Sep 17 00:00:00 2001 From: Rodrigo Braz Date: Wed, 17 Apr 2024 21:04:34 +0100 Subject: [PATCH 8/9] chore(justwatch): Leverage trakt to gather justwatch providers --- app/deleterr.py | 15 +++-- app/scripts/justwatch_providers.py | 88 +++++++++++++++++++++--------- 2 files changed, 73 insertions(+), 30 deletions(-) diff --git a/app/deleterr.py b/app/deleterr.py index bbc0700..4ebbd84 100644 --- a/app/deleterr.py +++ b/app/deleterr.py @@ -105,15 +105,22 @@ def main(): args, unknown = parser.parse_known_args() + config = load_config(args.config) + config.validate() + # If providers flag is set, gather JustWatch providers and exit if args.jw_providers: from app.scripts.justwatch_providers import gather_providers - gather_providers() - return + providers = gather_providers( + config.settings.get("trakt", {}).get("client_id"), + config.settings.get("trakt", {}).get("client_secret"), + ) - config = load_config(args.config) - config.validate() + print(providers) + logger.info("# of Trakt Providers: " + str(len(providers))) + + return Deleterr(config) diff --git a/app/scripts/justwatch_providers.py b/app/scripts/justwatch_providers.py index 155f310..6a914e6 100644 --- a/app/scripts/justwatch_providers.py +++ b/app/scripts/justwatch_providers.py @@ -1,34 +1,70 @@ from app.modules.justwatch import JustWatch +from app.modules.trakt import Trakt -SHOWS = [ - ("Loki", 2021), - ("Stranger Things", 2016), - ("Reacher", 2022), - ("Logan", 2017), - ("Severance", 2022), - ("Cobra Kai", 2018), - ("Bo Burnham: What", 2013), - ("Interstellar", 2014), - ("Eraserhead", 1977), - ("Life on Earth", 1979), - ("24", 2001), - ("Current Sea", 2020), -] - - -def gather_providers(): - # Create a JustWatch instance - justwatch = JustWatch("US", "en") + +def gather_providers(trakt_id, trakt_secret): + # Create a Trakt instance + trakt = Trakt( + trakt_id, + trakt_secret, + ) + + # Get the most popular shows + shows = trakt.get_all_items_for_url( + "show", + { + "max_items_per_list": 200, + "lists": [ + "https://trakt.tv/shows/trending", + "https://trakt.tv/shows/popular", + "https://trakt.tv/shows/watched/yearly", + "https://trakt.tv/shows/collected/yearly", + ], + }, + ) + + # List of country codes to check providers for + countries = [ + "US", + "BR", + "NG", + "IN", + "CN", + "RU", + "AU", + "PT", + "FR", + "DE", + "ES", + "IT", + "JP", + "KR", + "GB", + ] # Create a set to store the providers providers = set() - # Iterate shows and collect all the different providers in a set - for title, year in SHOWS: - result = justwatch.search_by_title_and_year(title, year, "show") - if result: - for offer in result.offers: - providers.add(offer.package.technical_name) + # Iterate over the countries + for country in countries: + # Create a JustWatch instance for the current country + justwatch = JustWatch(country, "en") + + # Iterate shows and collect all the different providers in a set + for show in shows: + try: + title = shows[show]["trakt"].title + year = shows[show]["trakt"].year + result = justwatch.search_by_title_and_year(title, year, "show") + if result: + for offer in result.offers: + providers.add(offer.package.technical_name) + except AttributeError: + # Skip if the show doesn't have a title or year + continue + except TypeError: + # There is a null error inside justwatch library, this is a workaround + continue # Print the providers - print(providers) + return providers From d3e85990e6cc18a31486fd4eb1254e452ccda46d Mon Sep 17 00:00:00 2001 From: Rodrigo Braz Date: Wed, 17 Apr 2024 21:15:51 +0100 Subject: [PATCH 9/9] chore(justwatch): Ignore coverage on scripts --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index bb6032e..bd84d93 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -8,7 +8,7 @@ sonar.python.version=3 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. sonar.sources=app/ -sonar.tests.exclusions=tests/** +sonar.exclusions=/app/scripts/** sonar.tests=tests/ sonar.language=python