diff --git a/RatS.egg-info/PKG-INFO b/RatS.egg-info/PKG-INFO index 1c161d8..60c0a7e 100644 --- a/RatS.egg-info/PKG-INFO +++ b/RatS.egg-info/PKG-INFO @@ -681,7 +681,7 @@ Description:

This project serves for analyzing, and transfering your ratings from one movie tracking / rating website to another. The goal of this project is to have a universal tool which can transfer your ratings from any site to another without - the need of any manual steps like configuring an API access or whatever. Just configure you credentials (see steps + the need of any manual steps like configuring an API access or whatever. Just configure your credentials (see steps below), start the tool and relax. This also works if your lists are marked as private, as this tool uses a browser to login and get the content. @@ -706,10 +706,27 @@ Description:

* Or execute `sudo ./InstallGeckodriver.sh`. For this you will need to have tar, wget and curl installed. - 1. Copy the `credentials.cfg.orig` file to `credentials.cfg` and insert your credentials for the sites you need there + 1. Set your credentials + + 1. Copy the `credentials.cfg.orig` file to `credentials.cfg` and insert your credentials for the sites you need there (without any quotation marks etc.). - Copying the file will conserve the possibility to do a `git pull` later on without overwriting your credentials. + Copying the file will conserve the possibility to do a `git pull` later on without overwriting your credentials. + + 1. Alternatively, you can set the credentials via environment variables. For example, if you want to set the + credentials for your IMDB and Trakt accounts you would need to set these environment variables: + ```sh + export IMDB_USERNAME=abc@def.de + export IMDB_PASWORD=def + export TRAKT_USERNAME=abc + export TRAKT_PASWORD=def + ``` + It is important to use the variable names completely in uppercase! + + 1. The third way is to set the environment variables when running the transfer script, like this: + `IMDB_USERNAME=abc@def.de IMDB_PASWORD=def TRAKT_USERNAME=abc TRAKT_PASWORD=def python3 transfer_ratings.py --source trakt --destination imdb` + + The credentials in environment variables are overruling the ones in the `credentials.cfg` file. 1. Execute the script with **Python3** `python3 transfer_ratings.py --source trakt --destination movielens` diff --git a/RatS/plex/plex_ratings_inserter.py b/RatS/plex/plex_ratings_inserter.py index 9e9bb14..db49454 100644 --- a/RatS/plex/plex_ratings_inserter.py +++ b/RatS/plex/plex_ratings_inserter.py @@ -1,4 +1,3 @@ -import math import urllib.request from bs4 import BeautifulSoup @@ -15,9 +14,14 @@ def __init__(self, args): super(PlexRatingsInserter, self).__init__(Plex(args), args) def _search_for_movie(self, movie): - search_url = 'http://{base_url}/search?local=1&query={search_params}'.format( - base_url=self.site.BASE_URL, search_params=urllib.request.quote(movie['title']) - ) + search_url = 'http://{base_url}/library/all?type=1&title={movie_title}' \ + '&X-Plex-Container-Start=0' \ + '&X-Plex-Container-Size=50' \ + '&X-Plex-Token={plex_token}'.format( + base_url=self.site.BASE_URL, + movie_title=urllib.request.quote(movie['title']), + plex_token=self.site.PLEX_TOKEN + ) self.site.browser.get(search_url) @@ -35,12 +39,13 @@ def _is_requested_movie(self, movie, search_result): if is_requested_movie: movie_id = search_result['ratingkey'] - movie_url = 'http://{base_url}/web/index.html#!/server/{server_id}/details/{library_path}{movie_id}'.format( - base_url=self.site.BASE_URL, - server_id=self.site.SERVER_ID, - library_path='%2Flibrary%2Fmetadata%2F', - movie_id=movie_id - ) + movie_url = 'http://{base_url}/web/index.html#!/server/{server_id}/details' \ + '?key={library_path}{movie_id}'.format( + base_url=self.site.BASE_URL, + server_id=self.site.SERVER_ID, + library_path='%2Flibrary%2Fmetadata%2F', + movie_id=movie_id + ) self.site.browser.get(movie_url) self._wait_for_movie_page_to_be_loaded() return True @@ -55,6 +60,13 @@ def _wait_for_movie_page_to_be_loaded(self): ) def _click_rating(self, my_rating): - stars = self.site.browser.find_element_by_class_name('rating').find_elements_by_css_selector('span.star') - star_index = math.ceil(int(my_rating) / 2) - 1 - stars[star_index].click() + movie_id = self.site.browser.current_url.split('%2Flibrary%2Fmetadata%2F')[-1] + rate_url = 'http://{base_url}/:/rate' \ + '?key={movie_id}&identifier=com.plexapp.plugins.library' \ + '&rating={my_rating}&X-Plex-Token={plex_token}'.format( + base_url=self.site.BASE_URL, + movie_id=movie_id, + my_rating=my_rating, + plex_token=self.site.PLEX_TOKEN + ) + self.site.browser.get(rate_url) diff --git a/RatS/plex/plex_ratings_parser.py b/RatS/plex/plex_ratings_parser.py index 483ed80..e546bed 100644 --- a/RatS/plex/plex_ratings_parser.py +++ b/RatS/plex/plex_ratings_parser.py @@ -22,13 +22,15 @@ def _get_movies_count(movie_ratings_page): def _get_ratings_page(self, i): page_size = 100 - page_start = i * page_size - return 'http://{base_url}/library/sections/{section_id}/all' \ - '?type=1&sort=rating:desc&X-Plex-Container-Start={page_start}&X-Plex-Container-Size={page_size}'.format( + page_start = (i - 1) * page_size + return 'http://{base_url}/library/all?type=1&userRating!=0' \ + '&X-Plex-Container-Start={page_start}' \ + '&X-Plex-Container-Size={page_size}' \ + '&X-Plex-Token={plex_token}'.format( base_url=self.site.BASE_URL, - section_id=self.site.MOVIE_SECTION_ID, page_start=page_start, - page_size=page_size + page_size=page_size, + plex_token=self.site.PLEX_TOKEN ) @staticmethod @@ -46,7 +48,7 @@ def _parse_movie_tile(self, movie_tile): movie[self.site.site_name.lower()]['my_rating'] = round(float(movie_tile['userrating'])) movie[self.site.site_name.lower()]['id'] = movie_tile['ratingkey'] movie[self.site.site_name.lower()]['url'] = \ - 'http://{base_url}/web/index.html#!/server/{server_id}/details/{library_path}{movie_id}'.format( + 'http://{base_url}/web/index.html#!/server/{server_id}/details?key={library_path}{movie_id}'.format( base_url=self.site.BASE_URL, server_id=self.site.SERVER_ID, library_path='%2Flibrary%2Fmetadata%2F', diff --git a/RatS/plex/plex_site.py b/RatS/plex/plex_site.py index 165f52e..ea7375f 100644 --- a/RatS/plex/plex_site.py +++ b/RatS/plex/plex_site.py @@ -1,44 +1,59 @@ -import sys +import re import time -from bs4 import BeautifulSoup +from selenium.webdriver.support import ui from RatS.base.base_site import Site class Plex(Site): def __init__(self, args): - login_form_selector = "//form[@id='user-account-form']" - self.LOGIN_USERNAME_SELECTOR = login_form_selector + "//input[@id='username']" + login_form_selector = "//form" + self.LOGIN_USERNAME_SELECTOR = login_form_selector + "//input[@id='email']" self.LOGIN_PASSWORD_SELECTOR = login_form_selector + "//input[@id='password']" self.LOGIN_BUTTON_SELECTOR = login_form_selector + "//button[@type='submit']" + self.LOGIN_METHOD_EMAIL_SELECTOR = "//button[@data-qa-id='signIn--email']" super(Plex, self).__init__(args) def _get_login_page_url(self): self.BASE_URL = self.config[self.site_name]['BASE_URL'] + ":" + self.config[self.site_name]['BASE_PORT'] return "http://{base_url}/web/index.html#!/login".format(base_url=self.BASE_URL) + def _insert_login_credentials(self): + time.sleep(2) + self.browser.find_element_by_xpath(self.LOGIN_METHOD_EMAIL_SELECTOR).click() + time.sleep(0.5) + Site._insert_login_credentials(self) + + def _user_is_not_logged_in(self): + return self.USERNAME not in self.browser.page_source + def _parse_configuration(self): - self.MOVIE_SECTION_ID = self._determine_movies_section_id() + self.PLEX_TOKEN = self._determine_plex_token() self.SERVER_ID = self._determine_server_id() - self.MY_RATINGS_URL = 'http://{base_url}/library/sections/{section_id}/all' \ - '?type=1&sort=rating:desc&X-Plex-Container-Start=0&X-Plex-Container-Size=100'.format( + self.MY_RATINGS_URL = 'http://{base_url}/library/all?type=1&userRating!=0' \ + '&X-Plex-Container-Start={page_start}' \ + '&X-Plex-Container-Size={page_size}' \ + '&X-Plex-Token={plex_token}'.format( base_url=self.BASE_URL, - section_id=self.MOVIE_SECTION_ID + page_start=0, + page_size=100, + plex_token=self.PLEX_TOKEN ) - def _determine_movies_section_id(self): - sys.stdout.write('\r===== ' + self.site_displayname + ': determine movie section') - sys.stdout.flush() + def _determine_plex_token(self): + self.browser.get('http://{base_url}/web/index.html#'.format(base_url=self.BASE_URL)) + wait = ui.WebDriverWait(self.browser, 600) + wait.until(lambda driver: driver.find_element_by_xpath("//button[@data-qa-id='metadataPosterMoreButton']")) - self.browser.get('http://{base_url}/library/sections'.format(base_url=self.BASE_URL)) - time.sleep(1) - media_sections = BeautifulSoup(self.browser.page_source, 'html.parser') - time.sleep(1) + self.browser.find_elements_by_xpath("//button[@data-qa-id='metadataPosterMoreButton']")[0].click() + self.browser.find_elements_by_xpath("//button[@role='menuitem']")[-1].click() + link_to_xml = self.browser.find_element_by_xpath("//div[@class='modal-footer']//a").get_attribute('href') + plex_token = re.findall(r'X-Plex-Token=(\w+)', link_to_xml)[0] - return media_sections.find('directory', attrs={'type': 'movie'})['key'] + return plex_token def _determine_server_id(self): self.browser.get('http://{base_url}/web/index.html#!/settings/server'.format(base_url=self.BASE_URL)) - time.sleep(1) + time.sleep(2) return self.browser.current_url.split('/')[-2] diff --git a/tests/unit/plex/test_plex_ratings_inserter.py b/tests/unit/plex/test_plex_ratings_inserter.py index 8080711..5d32860 100644 --- a/tests/unit/plex/test_plex_ratings_inserter.py +++ b/tests/unit/plex/test_plex_ratings_inserter.py @@ -27,12 +27,13 @@ def setUp(self): self.search_result_tile_list = [result_tile.read()] @patch('RatS.plex.plex_ratings_inserter.Plex._determine_server_id') - @patch('RatS.plex.plex_ratings_inserter.Plex._determine_movies_section_id') + @patch('RatS.plex.plex_ratings_inserter.Plex') @patch('RatS.base.base_ratings_inserter.RatingsInserter.__init__') @patch('RatS.utils.browser_handler.Firefox') - def test_init(self, browser_mock, base_init_mock, section_id_mock, server_id_mock): + def test_init(self, browser_mock, base_init_mock, site_mock, server_id_mock): PlexRatingsInserter(None) + self.assertTrue(site_mock.called) self.assertTrue(base_init_mock.called) @patch('RatS.base.base_ratings_inserter.RatingsInserter._print_progress_bar') diff --git a/tests/unit/plex/test_plex_ratings_parser.py b/tests/unit/plex/test_plex_ratings_parser.py index 6d64e28..a4b9007 100644 --- a/tests/unit/plex/test_plex_ratings_parser.py +++ b/tests/unit/plex/test_plex_ratings_parser.py @@ -19,12 +19,13 @@ def setUp(self): self.ratings_tile = BeautifulSoup(ratings_tile.read(), 'html.parser').find('video', attrs={'type': 'movie'}) @patch('RatS.plex.plex_ratings_inserter.Plex._determine_server_id') - @patch('RatS.plex.plex_ratings_inserter.Plex._determine_movies_section_id') + @patch('RatS.plex.plex_ratings_parser.Plex') @patch('RatS.base.base_ratings_parser.RatingsParser.__init__') @patch('RatS.utils.browser_handler.Firefox') - def test_init(self, browser_mock, base_init_mock, section_id_mock, server_id_mock): + def test_init(self, browser_mock, base_init_mock, site_mock, server_id_mock): PlexRatingsParser(None) + self.assertTrue(site_mock.called) self.assertTrue(base_init_mock.called) @patch('RatS.plex.plex_ratings_parser.PlexRatingsParser._print_progress_bar') @@ -69,4 +70,4 @@ def test_parser_single_movie(self, site_mock, base_init_mock, browser_mock): self.assertEqual('19542', movie['plex']['id']) self.assertEqual('http://localhost:12345/web/index.html#!' '/server/ThisIsAMockUUID/' - 'details/%2Flibrary%2Fmetadata%2F19542', movie['plex']['url']) + 'details?key=%2Flibrary%2Fmetadata%2F19542', movie['plex']['url']) diff --git a/tests/unit/plex/test_plex_site.py b/tests/unit/plex/test_plex_site.py index f19e0eb..5ca91f7 100644 --- a/tests/unit/plex/test_plex_site.py +++ b/tests/unit/plex/test_plex_site.py @@ -11,21 +11,6 @@ class PlexSiteTest(TestCase): def setUp(self): if not os.path.exists(os.path.join(TESTDATA_PATH, 'exports')): os.makedirs(os.path.join(TESTDATA_PATH, 'exports')) - with open(os.path.join(TESTDATA_PATH, 'plex', 'sections.xml'), encoding='UTF-8') as server_sections: - self.server_sections = server_sections.read() - - @patch('RatS.plex.plex_site.Plex._parse_configuration') - @patch('RatS.utils.browser_handler.Firefox') - @patch('RatS.base.base_site.Site._init_browser') - def test_determine_movie_section_id(self, init_browser_mock, browser_mock, configuration_mock): - browser_mock.page_source = self.server_sections - site = Plex(None) - site.browser = browser_mock - site.BASE_URL = 'localhost' - - result = site._determine_movies_section_id() # pylint: disable=protected-access - - self.assertEqual('5', result) @patch('RatS.plex.plex_site.Plex._parse_configuration') @patch('RatS.utils.browser_handler.Firefox')