def test_movie_list_movies(self, api_client):
        # Get non existent list
        rsp = api_client.get('/movie_list/1/movies/')
        assert rsp.status_code == 404, 'Response code is %s' % rsp.status_code

        payload = {'name': 'name'}

        # Create list
        rsp = api_client.json_post('/movie_list/', data=json.dumps(payload))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code

        identifier = {'imdb_id': 'tt1234567'}
        movie_data = {'title': 'title',
                      'original_url': 'http://test.com',
                      'movie_identifiers': [identifier]}

        # Add movie to list
        rsp = api_client.json_post('/movie_list/1/movies/', data=json.dumps(movie_data))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code

        # Get movies from list
        rsp = api_client.get('/movie_list/1/movies/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        returned_identifier = json.loads(rsp.data)['movies'][0]['movies_list_ids'][0]
        assert returned_identifier['id_name'], returned_identifier['id_value'] == identifier.items()[0]
    def test_change_password(self, execute_task, api_client, schema_match):
        weak_password = {'password': '******'}
        medium_password = {'password': '******'}
        strong_password = {'password': '******'}

        rsp = api_client.json_put('/user/', data=json.dumps(weak_password))
        assert rsp.status_code == 400
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors

        rsp = api_client.json_put('/user/', data=json.dumps(medium_password))
        assert rsp.status_code == 200
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors

        rsp = api_client.json_put('/user/', data=json.dumps(strong_password))
        assert rsp.status_code == 200
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors
    def test_movie_list_movies(self, api_client, schema_match):
        payload = {'name': 'name'}

        # Create list
        rsp = api_client.json_post('/movie_list/', data=json.dumps(payload))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code

        movie_data = {'movie_name': 'title'}

        # Add movie to list
        rsp = api_client.json_post('/movie_list/1/movies/', data=json.dumps(movie_data))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code
        movie = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.movie_list_object, movie)
        assert not errors

        # Get movies from list
        rsp = api_client.get('/movie_list/1/movies/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.return_movies, data)
        assert not errors

        assert data[0] == movie

        # Get movies from non-existent list
        rsp = api_client.get('/movie_list/10/movies/')
        assert rsp.status_code == 404, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(base_message, data)
        assert not errors
Beispiel #4
0
    def test_schedules_id_put(self, mocked_save_config, api_client, schema_match):
        # Get schedules to get their IDs
        rsp = api_client.get('/schedules/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.schedules_list, data)
        assert not errors

        schedule_id = data[0]['id']
        payload = {'tasks': ['test2', 'test3'], 'interval': {'minutes': 10}}
        rsp = api_client.json_put('/schedules/{}/'.format(schedule_id), data=json.dumps(payload))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.schedule_object, data)
        assert not errors
        assert mocked_save_config.called

        del data['id']
        assert data == payload

        rsp = api_client.json_put('/schedules/1011/', data=json.dumps(payload))
        assert rsp.status_code == 404, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors
    def test_movie_list_movies_with_identifiers(self, api_client, schema_match):
        payload = {'name': 'name'}

        # Create list
        rsp = api_client.json_post('/movie_list/', data=json.dumps(payload))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code

        identifier = {'imdb_id': 'tt1234567'}
        movie_data = {'movie_name': 'title',
                      'movie_identifiers': [identifier]}

        # Add movie to list
        rsp = api_client.json_post('/movie_list/1/movies/', data=json.dumps(movie_data))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.movie_list_object, data)
        assert not errors

        # Get movies from list
        rsp = api_client.get('/movie_list/1/movies/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.return_movies, data)
        assert not errors

        returned_identifier = data['movies'][0]['movies_list_ids'][0]
        assert returned_identifier['id_name'], returned_identifier['id_value'] == identifier.items()[0]
Beispiel #6
0
    def send_push(self, task, api_key, title, body, url=None, destination=None, destination_type=None):

        if url:
            push_type = 'link'
        else:
            push_type = 'note'

        data = {'type': push_type, 'title': title, 'body': body}
        if url:
            data['url'] = url
        if destination:
            data[destination_type] = destination

        # Check for test mode
        if task.options.test:
            log.info('Test mode. Pushbullet notification would be:')
            log.info('    API Key: %s' % api_key)
            log.info('    Type: %s' % push_type)
            log.info('    Title: %s' % title)
            log.info('    Body: %s' % body)
            if destination:
                log.info('    Destination: %s (%s)' % (destination, destination_type))
            if url:
                log.info('    URL: %s' % url)
            log.info('    Raw Data: %s' % json.dumps(data))
            # Test mode.  Skip remainder.
            return

        # Make the request
        headers = {
            'Authorization': b'Basic %s' % base64.b64encode(api_key.encode('ascii')),
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'User-Agent': 'Flexget'
        }
        response = task.requests.post(pushbullet_url, headers=headers, data=json.dumps(data), raise_status=False)

        # Check if it succeeded
        request_status = response.status_code

        # error codes and messages from Pushbullet API
        if request_status == 200:
            log.debug('Pushbullet notification sent')
        elif request_status == 500:
            log.warning('Pushbullet notification failed, Pushbullet API having issues')
            # TODO: Implement retrying. API requests 5 seconds between retries.
        elif request_status >= 400:
            error = 'Unknown error'
            if response.content:
                try:
                    error = response.json()['error']['message']
                except ValueError as e:
                    error = 'Unknown Error (Invalid JSON returned): %s' % e
            log.error('Pushbullet API error: %s' % error)
        else:
            log.error('Unknown error when sending Pushbullet notification')
    def test_movie_list_movie(self, api_client, schema_match):
        payload = {'name': 'name'}

        # Create list
        rsp = api_client.json_post('/movie_list/', data=json.dumps(payload))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code

        identifier = {'imdb_id': 'tt1234567'}
        movie_data = {'movie_name': 'title',
                      'movie_identifiers': [identifier]}

        # Add movie to list
        rsp = api_client.json_post('/movie_list/1/movies/', data=json.dumps(movie_data))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code

        # Get specific movie from list
        rsp = api_client.get('/movie_list/1/movies/1/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.movie_list_object, data)
        assert not errors

        returned_identifier = data['movies_list_ids'][0]
        assert returned_identifier['id_name'], returned_identifier['id_value'] == identifier.items()[0]

        identifiers = [{'trakt_movie_id': '12345'}]

        # Change specific movie from list
        rsp = api_client.json_put('/movie_list/1/movies/1/', data=json.dumps(identifiers))
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.movie_list_object, data)
        assert not errors

        returned_identifier = data['movies_list_ids'][0]
        assert returned_identifier['id_name'], returned_identifier['id_value'] == identifiers[0].items()

        # Delete specific movie from list
        rsp = api_client.delete('/movie_list/1/movies/1/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(empty_response, data)
        assert not errors

        # Get non existent movie from list
        rsp = api_client.get('/movie_list/1/movies/1/')
        assert rsp.status_code == 404, 'Response code is %s' % rsp.status_code

        # Delete non existent movie from list
        rsp = api_client.delete('/movie_list/1/movies/1/')
        assert rsp.status_code == 404, 'Response code is %s' % rsp.status_code
 def on_task_output(self, task, config):
     """Add accepted episodes and/or movies to uoccin's collection"""
     series = {}
     movies = {}
     for entry in task.accepted:
         if all(field in entry for field in ['tvdb_id', 'series_season', 'series_episode']):
             eid = '%s.S%02dE%02d' % (entry['tvdb_id'], entry['series_season'], entry['series_episode'])
             if not eid in series: # we can have more than one (different release/quality)
                 series[eid] = []
             if self.acquire and 'subtitles' in entry:
                 subs = series[eid]
                 series[eid] = list(set(subs + entry['subtitles']))
         elif all(field in entry for field in ['imdb_id', 'movie_name']):
             eid = entry['imdb_id']
             if not eid in movies: # we can have more than one (different release/quality)
                 movies[eid] = {'name': entry.get('imdb_name', entry['movie_name'])}
             if self.acquire and 'subtitles' in entry:
                 subs = movies[eid]['subtitles'] if 'subtitles' in movies[eid] else []
                 movies[eid]['subtitles'] = list(set(subs + entry['subtitles']))
     if series:
         dest = os.path.join(config, 'series.collected.json')
         data = {}
         if os.path.exists(dest):
             with open(dest, 'r') as f:
                 data = json.load(f)
         for eid in series:
             if self.acquire:
                 log.info('adding/updating episode %s to Uoccin collection' % eid)
                 data[eid] = series[eid]
             elif eid in data:
                 log.info('removing episode %s from Uoccin collection' % eid)
                 data.pop(eid)
         text = json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))
         with open(dest, 'w') as f:
             f.write(text)
         log.debug('Uoccin episodes collection updated')
     if movies:
         dest = os.path.join(config, 'movies.collected.json')
         data = {}
         if os.path.exists(dest):
             with open(dest, 'r') as f:
                 data = json.load(f)
         for eid in movies:
             if self.acquire:
                 log.info('adding/updating movie %s to Uoccin collection' % eid)
                 data[eid] = movies[eid]
             elif eid in data:
                 log.info('removing movie %s from Uoccin collection' % eid)
                 data.pop(eid)
         text = json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))
         with open(dest, 'w') as f:
             f.write(text)
         log.debug('Uoccin movies collection updated')
Beispiel #9
0
    def test_change_password(self, execute_task, api_client):
        weak_password = {'password': '******'}
        medium_password = {'password': '******'}
        strong_password = {'password': '******'}

        rsp = api_client.json_put('/user/', data=json.dumps(weak_password))
        assert rsp.status_code == 500

        rsp = api_client.json_put('/user/', data=json.dumps(medium_password))
        assert rsp.status_code == 200

        rsp = api_client.json_put('/user/', data=json.dumps(strong_password))
        assert rsp.status_code == 200
    def test_movie_list_list(self, api_client, schema_match):
        # No params
        rsp = api_client.get('/movie_list/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.return_lists, data)
        assert not errors

        assert data == []

        # Named param
        rsp = api_client.get('/movie_list/?name=name')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.return_lists, data)
        assert not errors

        payload = {'name': 'test'}

        # Create list
        rsp = api_client.json_post('/movie_list/', data=json.dumps(payload))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.list_object, data)
        assert not errors

        values = {
            'name': 'test',
            'id': 1
        }
        for field, value in values.items():
            assert data.get(field) == value

        rsp = api_client.get('/movie_list/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.return_lists, data)
        assert not errors

        for field, value in values.items():
            assert data[0].get(field) == value

        # Try to Create existing list
        rsp = api_client.json_post('/movie_list/', data=json.dumps(payload))
        assert rsp.status_code == 409, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(base_message, data)
        assert not errors
Beispiel #11
0
    def on_task_input(self, task, config):
        # Don't edit the config, or it won't pass validation on rerun
        url_params = config.copy()
        if 'movies' in config and 'series' in config:
            raise PluginError('Cannot use both series list and movies list in the same task.')
        if 'movies' in config:
            url_params['data_type'] = 'movies'
            url_params['list_type'] = config['movies']
            map = self.movie_map
        elif 'series' in config:
            url_params['data_type'] = 'shows'
            url_params['list_type'] = config['series']
            map = self.series_map
        elif 'custom' in config:
            url_params['data_type'] = 'custom'
            # Do some translation from visible list name to prepare for use in url
            list_name = config['custom'].lower()
            # These characters are just stripped in the url
            for char in '!@#$%^*()[]{}/=?+\\|-_':
                list_name = list_name.replace(char, '')
            # These characters get replaced
            list_name = list_name.replace('&', 'and')
            list_name = list_name.replace(' ', '-')
            url_params['list_type'] = list_name
            # Map type is per item in custom lists
        else:
            raise PluginError('Must define movie or series lists to retrieve from trakt.')

        url = 'http://api.trakt.tv/user/'
        auth = None
        if url_params['data_type'] == 'custom':
            url += 'list.json/%(api_key)s/%(username)s/%(list_type)s'
        elif url_params['list_type'] == 'watchlist':
            url += 'watchlist/%(data_type)s.json/%(api_key)s/%(username)s'
        else:
            url += 'library/%(data_type)s/%(list_type)s.json/%(api_key)s/%(username)s'
        url = url % url_params

        if 'password' in config:
            auth = {'username': config['username'],
                    'password': hashlib.sha1(config['password']).hexdigest()}

        entries = []
        log.verbose('Retrieving list %s %s...' % (url_params['data_type'], url_params['list_type']))

        result = task.requests.get(url, data=json.dumps(auth))
        try:
            data = task.requests.post(url, data=json.dumps(auth)).json
        except RequestException, e:
            raise PluginError('Could not retrieve list from trakt (%s)' % e.message)
Beispiel #12
0
    def test_pending_api_put_entry(self, api_client, schema_match):
        e1 = Entry(title='test.title1', url='http://bla.com')
        e2 = Entry(title='test.title1', url='http://bla.com')

        with Session() as session:
            pe1 = PendingEntry('test_task', e1)
            pe2 = PendingEntry('test_task', e2)
            session.bulk_save_objects([pe1, pe2])

        payload = {'operation': 'approve'}

        rsp = api_client.json_put('/pending/1/', data=json.dumps(payload))
        assert rsp.status_code == 201

        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_entry_object, data)
        assert not errors

        assert data['approved'] is True

        rsp = api_client.json_put('/pending/1/', data=json.dumps(payload))
        assert rsp.status_code == 400

        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors

        payload = {'operation': 'reject'}

        rsp = api_client.json_put('/pending/1/', data=json.dumps(payload))
        assert rsp.status_code == 201

        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_entry_object, data)
        assert not errors

        assert data['approved'] is False

        rsp = api_client.json_put('/pending/1/', data=json.dumps(payload))
        assert rsp.status_code == 400

        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors
Beispiel #13
0
    def send_post(self, task, webhook_url, text, channel, username,
                  icon_emoji):

        data = {'text': text}
        if channel:
            data['channel'] = channel
        if username:
            data['username'] = username
        if icon_emoji:
            data['icon_emoji'] = icon_emoji

        if task.options.test:
            log.info('Test mode. Slack notification would be:')
            log.info('    Webhook URL: {0}'.format(webhook_url))
            log.info('    Text: {0}'.format(text))
            if channel:
                log.info('    Channel: {0}'.format(channel))
            if username:
                log.info('    Username: {0}'.format(username))
            if icon_emoji:
                log.info('    Icon Emoji: :{0}:'.format(icon_emoji))
            log.info('    Raw POST Data: {0}'.format(json.dumps(data)))
            # Early return (test mode)
            return

        headers = {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'User-Agent': 'Flexget'
        }
        try:
            response = task.requests.post(webhook_url, headers=headers,
                                          data=json.dumps(data),
                                          raise_status=False)
        except ConnectionError as e:
            log.error('Unable to connect to Slack API: {0}'.format(e.message))
            return

        response_code = response.status_code

        if response_code == 200:
            log.debug('Slack notification sent')
        elif response_code == 500:
            log.warning('Slack notification failed: server problem')
        elif response_code >= 400:
            log.error('Slack API error {0}: {1}'.format(response_code, response.content))
        else:
            log.error('Unknown error when sending Slack notification: {0}'.format(response_code))
Beispiel #14
0
def upgrade(ver, session):
    if ver is None:
        # Upgrade to version 0 was a failed attempt at cleaning bad entries from our table, better attempt in ver 1
        ver = 1
    if ver == 1:
        table = table_schema('delay', session)
        table_add_column(table, 'json', Unicode, session)
        # Make sure we get the new schema with the added column
        table = table_schema('delay', session)
        failures = 0
        for row in session.execute(select([table.c.id, table.c.entry])):
            try:
                p = pickle.loads(row['entry'])
                session.execute(
                    table.update()
                    .where(table.c.id == row['id'])
                    .values(json=json.dumps(p, encode_datetime=True))
                )
            except (KeyError, ImportError):
                failures += 1
        if failures > 0:
            log.error(
                'Error upgrading %s pickle objects. Some delay information has been lost.'
                % failures
            )
        ver = 2

    return ver
Beispiel #15
0
def get_session(username=None, password=None):
    """Creates a requests session which is authenticated to trakt."""
    session = Session()
    session.headers = {
        'Content-Type': 'application/json',
        'trakt-api-version': 2,
        'trakt-api-key': API_KEY
    }
    if username:
        session.headers['trakt-user-login'] = username
    if username and password:
        auth = {'login': username, 'password': password}
        try:
            r = session.post(urljoin(API_URL, 'auth/login'), data=json.dumps(auth))
        except Timeout:  # requests.exceptions.Timeout
            raise plugin.PluginError('Authentication timed out to trakt')
        except RequestException as e:
            if hasattr(e, 'response') and e.response.status_code in [401, 403]:
                raise plugin.PluginError('Authentication to trakt failed, check your username/password: %s' % e.args[0])
            else:
                raise plugin.PluginError('Authentication to trakt failed: %s' % e.args[0])
        try:
            session.headers['trakt-user-token'] = r.json()['token']
        except (ValueError, KeyError):
            raise plugin.PluginError('Got unexpected response content while authorizing to trakt: %s' % r.text)
    return session
def upgrade(ver, session):
    if ver is None:
        # Upgrade to version 0 was a failed attempt at cleaning bad entries from our table, better attempt in ver 1
        ver = 0
    if ver == 0:
        # Remove any values that are not loadable.
        table = table_schema('simple_persistence', session)
        for row in session.execute(select([table.c.id, table.c.plugin, table.c.key, table.c.value])):
            try:
                pickle.loads(row['value'])
            except Exception as e:
                log.warning('Couldn\'t load %s:%s removing from db: %s' % (row['plugin'], row['key'], e))
                session.execute(table.delete().where(table.c.id == row['id']))
        ver = 1
    if ver == 1:
        log.info('Creating index on simple_persistence table.')
        create_index('simple_persistence', session, 'feed', 'plugin', 'key')
        ver = 2
    if ver == 2 or ver == 3:
        table = table_schema('simple_persistence', session)
        table_add_column(table, 'json', Unicode, session)
        # Make sure we get the new schema with the added column
        table = table_schema('simple_persistence', session)
        for row in session.execute(select([table.c.id, table.c.value])):
            try:
                p = pickle.loads(row['value'])
                session.execute(table.update().where(table.c.id == row['id']).values(
                    json=json.dumps(p, encode_datetime=True)))
            except KeyError as e:
                log.error('Unable error upgrading simple_persistence pickle object due to %s' % str(e))

        ver = 4
    return ver
Beispiel #17
0
    def test_seen_delete_all(self, mock_seen_search, api_client):
        session = Session()
        entry_list = session.query(SeenEntry).join(SeenField)
        mock_seen_search.return_value = entry_list

        # No params
        rsp = api_client.delete('/seen/')
        assert rsp.status_code == 404, 'Response code is %s' % rsp.status_code

        fields = {
            'url': 'http://test.com/file.torrent',
            'title': 'Test.Title',
            'torrent_hash_id': 'dsfgsdfg34tq34tq34t'
        }
        entry = {
            'local': False,
            'reason': 'test_reason',
            'task': 'test_task',
            'title': 'Test.Title',
            'fields': fields
        }

        rsp = api_client.json_post('/seen/', data=json.dumps(entry))
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        # With value
        rsp = api_client.delete('/seen/?value=Test.Title')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        assert mock_seen_search.call_count == 2, 'Should have 2 calls, is actually %s' % mock_seen_search.call_count
Beispiel #18
0
def upgrade(ver, session):
    if ver is None:
        # Make sure there is no data we can't load in the backlog table
        backlog_table = table_schema('backlog', session)
        try:
            for item in session.query('entry').select_from(backlog_table).all():
                pickle.loads(item.entry)
        except (ImportError, TypeError):
            # If there were problems, we can drop the data.
            log.info('Backlog table contains unloadable data, clearing old data.')
            session.execute(backlog_table.delete())
        ver = 0
    if ver == 0:
        backlog_table = table_schema('backlog', session)
        log.info('Creating index on backlog table.')
        Index('ix_backlog_feed_expire', backlog_table.c.feed, backlog_table.c.expire).create(bind=session.bind)
        ver = 1
    if ver == 1:
        table = table_schema('backlog', session)
        table_add_column(table, 'json', Unicode, session)
        # Make sure we get the new schema with the added column
        table = table_schema('backlog', session)
        for row in session.execute(select([table.c.id, table.c.entry])):
            try:
                p = pickle.loads(row['entry'])
                session.execute(table.update().where(table.c.id == row['id']).values(
                    json=json.dumps(p, encode_datetime=True)))
            except KeyError as e:
                log.error('Unable error upgrading backlog pickle object due to %s' % str(e))

        ver = 2
    return ver
Beispiel #19
0
    def search(self, task, entry, config):
        api_key = config

        searches = entry.get('search_strings', [entry['title']])

        if 'series_name' in entry:
            search = {'category': 'Episode'}
            if 'tvdb_id' in entry:
                search['tvdb'] = entry['tvdb_id']
            elif 'tvrage_id' in entry:
                search['tvrage'] = entry['tvrage_id']
            else:
                search['series'] = entry['series_name']
            if 'series_id' in entry:
                # BTN wants an ep style identifier even for sequence shows
                if entry.get('series_id_type') == 'sequence':
                    search['name'] = 'S01E%02d' % entry['series_id']
                else:
                    search['name'] = entry['series_id'] + '%' # added wildcard search for better results.
            searches = [search]
            # If searching by series name ending in a parenthetical, try again without it if there are no results.
            if search.get('series') and search['series'].endswith(')'):
                match = re.match('(.+)\([^\(\)]+\)$', search['series'])
                if match:
                    searches.append(dict(search, series=match.group(1).strip()))


        results = set()
        for search in searches:
            data = json.dumps({'method': 'getTorrents', 'params': [api_key, search], 'id': 1})
            try:
                r = task.requests.post('http://api.btnapps.net/',
                                       data=data, headers={'Content-type': 'application/json'})
            except requests.RequestException as e:
                log.error('Error searching btn: %s' % e)
                continue
            content = r.json()
            if not content or not content['result']:
                log.debug('No results from btn')
                if content and content.get('error'):
                    log.error('Error searching btn: %s' % content['error'].get('message', content['error']))
                continue
            if 'torrents' in content['result']:
                for item in content['result']['torrents'].itervalues():
                    entry = Entry()
                    entry['title'] = item['ReleaseName']
                    entry['title'] += ' '.join(['', item['Resolution'], item['Source'], item['Codec']])
                    entry['url'] = item['DownloadURL']
                    entry['torrent_seeds'] = int(item['Seeders'])
                    entry['torrent_leeches'] = int(item['Leechers'])
                    entry['torrent_info_hash'] = item['InfoHash']
                    entry['search_sort'] = torrent_availability(entry['torrent_seeds'], entry['torrent_leeches'])
                    if item['TvdbID'] and int(item['TvdbID']):
                        entry['tvdb_id'] = int(item['TvdbID'])
                    if item['TvrageID'] and int(item['TvrageID']):
                        entry['tvrage_id'] = int(item['TvrageID'])
                    results.add(entry)
                # Don't continue searching if this search yielded results
                break
        return results
Beispiel #20
0
 def setter(self, entry):
     if isinstance(entry, Entry) or isinstance(entry, dict):
         setattr(
             self, name, unicode(json.dumps(only_builtins(dict(entry)), encode_datetime=True))
         )
     else:
         raise TypeError('%r is not of type Entry or dict.' % type(entry))
Beispiel #21
0
    def search(self, entry, config):
        api_key = config

        searches = entry.get('search_strings', [entry['title']])

        if 'series_name' in entry:
            search = {'series': entry['series_name']}
            if 'series_id' in entry:
                search['name'] = entry['series_id']
            searches = [search]

        results = []
        for search in searches:
            data = json.dumps({'method': 'getTorrents', 'params': [api_key, search], 'id': 1})
            try:
                r = session.post('http://api.btnapps.net/', data=data, headers={'Content-type': 'application/json'})
            except requests.RequestException as e:
                log.error('Error searching btn: %s' % e)
                continue
            content = r.json()
            if content['result']['results']:
                for item in content['result']['torrents'].itervalues():
                    if item['Category'] != 'Episode':
                        continue
                    entry = Entry()
                    entry['title'] = item['ReleaseName']
                    entry['url'] = item['DownloadURL']
                    entry['torrent_seeds'] = int(item['Seeders'])
                    entry['torrent_leeches'] = int(item['Leechers'])
                    entry['torrent_info_hash'] = item['InfoHash']
                    entry['search_sort'] = torrent_availability(entry['torrent_seeds'], entry['torrent_leeches'])
                    if item['TvdbID']:
                        entry['tvdb_id'] = int(item['TvdbID'])
                    results.append(entry)
        return results
    def test_movie_list_movie(self, api_client):
        payload = {'name': 'name'}

        # Create list
        rsp = api_client.json_post('/movie_list/', data=json.dumps(payload))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code

        identifier = {'imdb_id': 'tt1234567'}
        movie_data = {'title': 'title',
                      'original_url': 'http://test.com',
                      'movie_identifiers': [identifier]}

        # Add movie to list
        rsp = api_client.json_post('/movie_list/1/movies/', data=json.dumps(movie_data))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code

        # Get movies from list
        rsp = api_client.get('/movie_list/1/movies/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code
        returned_identifier = json.loads(rsp.get_data(as_text=True))['movies'][0]['movies_list_ids'][0]
        assert returned_identifier['id_name'], returned_identifier['id_value'] == identifier.items()[0]

        # Get specific movie from list
        rsp = api_client.get('/movie_list/1/movies/1/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code
        returned_identifier = json.loads(rsp.get_data(as_text=True))['movies_list_ids'][0]
        assert returned_identifier['id_name'], returned_identifier['id_value'] == identifier.items()[0]

        identifiers = [{'trakt_movie_id': '12345'}]

        # Change specific movie from list
        rsp = api_client.json_put('/movie_list/1/movies/1/', data=json.dumps(identifiers))
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code
        returned_identifier = json.loads(rsp.get_data(as_text=True))['movies_list_ids'][0]
        assert returned_identifier['id_name'], returned_identifier['id_value'] == identifiers[0].items()

        # Delete specific movie from list
        rsp = api_client.delete('/movie_list/1/movies/1/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        # Get non existent movie from list
        rsp = api_client.get('/movie_list/1/movies/1/')
        assert rsp.status_code == 404, 'Response code is %s' % rsp.status_code

        # Delete non existent movie from list
        rsp = api_client.delete('/movie_list/1/movies/1/')
        assert rsp.status_code == 404, 'Response code is %s' % rsp.status_code
Beispiel #23
0
    def process_notifications(self, task, entries, config):
        for entry in entries:
            if task.manager.options.test:
                log.info("Would send RapidPush notification about: %s", entry['title'])
                continue

            log.info("Send RapidPush notification about: %s", entry['title'])
            apikey = entry.get('apikey', config['apikey'])
            if isinstance(apikey, list):
                apikey = ','.join(apikey)

            priority = entry.get('priority', config['priority'])

            category = entry.get('category', config['category'])
            try:
                category = entry.render(category)
            except RenderError as e:
                log.error('Error setting RapidPush category: %s' % e)

            title = config['title']
            try:
                title = entry.render(title)
            except RenderError as e:
                log.error('Error setting RapidPush title: %s' % e)

            message = config['message']
            try:
                message = entry.render(message)
            except RenderError as e:
                log.error('Error setting RapidPush message: %s' % e)

            group = entry.get('group', config['group'])
            try:
                group = entry.render(group)
            except RenderError as e:
                log.error('Error setting RapidPush group: %s' % e)

            # Send the request
            data_string = json.dumps({
                'title': title,
                'message': message,
                'priority': priority,
                'category': category,
                'group': group})
            data = {'apikey': apikey, 'command': 'notify', 'data': data_string}
            response = task.requests.post(url, headers=headers, data=data, raise_status=False)

            json_data = response.json()
            if json_data.has_key('code'):
                if json_data['code'] == 200:
                    log.debug("RapidPush message sent")
                else:
                    log.error(json_data['desc'] + " (" + str(json_data['code']) + ")")
            else:
                for item in json_data:
                    if json_data[item]['code'] == 200:
                        log.debug(item + ": RapidPush message sent")
                    else:
                        log.error(item + ": " + json_data[item]['desc'] + " (" + str(json_data[item]['code']) + ")")
    def test_adding_same_movie(self, api_client, schema_match):
        payload = {'name': 'test'}

        # Create list
        rsp = api_client.json_post('/movie_list/', data=json.dumps(payload))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.list_object, data)
        assert not errors

        movie = {'movie_name': 'test movie',
                 'movie_year': 2000}

        # Add movie to list
        rsp = api_client.json_post('/movie_list/1/movies/', data=json.dumps(movie))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.movie_list_object, data)
        assert not errors

        # Try to add it again
        rsp = api_client.json_post('/movie_list/1/movies/', data=json.dumps(movie))
        assert rsp.status_code == 409, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(base_message, data)
        assert not errors

        movie_2 = copy.deepcopy(movie)
        movie_2['movie_year'] = 1999

        # Add same movie name, different year
        rsp = api_client.json_post('/movie_list/1/movies/', data=json.dumps(movie_2))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.movie_list_object, data)
        assert not errors

        movie_3 = copy.deepcopy(movie)
        del movie_3['movie_year']

        # Add same movie, no year
        rsp = api_client.json_post('/movie_list/1/movies/', data=json.dumps(movie_3))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.movie_list_object, data)
        assert not errors
Beispiel #25
0
    def test_schedules_id_put(self, api_client, schema_match):
        payload = {'tasks': ['test2', 'test3'], 'interval': {'minutes': 10}}
        rsp = api_client.json_put('/schedules/1/', data=json.dumps(payload))
        assert rsp.status_code == 409, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors
Beispiel #26
0
 def check_auth():
     if (
         task.requests.post(
             "http://api.trakt.tv/account/test/" + config["api_key"], data=json.dumps(auth), raise_status=False
         ).status_code
         != 200
     ):
         raise PluginError("Authentication to trakt failed.")
Beispiel #27
0
    def test_movie_list_movie(self, api_client):
        payload = {"name": "name"}

        # Create list
        rsp = api_client.json_post("/movie_list/", data=json.dumps(payload))
        assert rsp.status_code == 201, "Response code is %s" % rsp.status_code

        identifier = {"imdb_id": "tt1234567"}
        movie_data = {"title": "title", "original_url": "http://test.com", "movie_identifiers": [identifier]}

        # Add movie to list
        rsp = api_client.json_post("/movie_list/1/movies/", data=json.dumps(movie_data))
        assert rsp.status_code == 201, "Response code is %s" % rsp.status_code

        # Get movies from list
        rsp = api_client.get("/movie_list/1/movies/")
        assert rsp.status_code == 200, "Response code is %s" % rsp.status_code
        returned_identifier = json.loads(rsp.get_data(as_text=True))["movies"][0]["movies_list_ids"][0]
        assert returned_identifier["id_name"], returned_identifier["id_value"] == identifier.items()[0]

        # Get specific movie from list
        rsp = api_client.get("/movie_list/1/movies/1/")
        assert rsp.status_code == 200, "Response code is %s" % rsp.status_code
        returned_identifier = json.loads(rsp.get_data(as_text=True))["movies_list_ids"][0]
        assert returned_identifier["id_name"], returned_identifier["id_value"] == identifier.items()[0]

        identifiers = [{"trakt_movie_id": "12345"}]

        # Change specific movie from list
        rsp = api_client.json_put("/movie_list/1/movies/1/", data=json.dumps(identifiers))
        assert rsp.status_code == 200, "Response code is %s" % rsp.status_code
        returned_identifier = json.loads(rsp.get_data(as_text=True))["movies_list_ids"][0]
        assert returned_identifier["id_name"], returned_identifier["id_value"] == identifiers[0].items()

        # Delete specific movie from list
        rsp = api_client.delete("/movie_list/1/movies/1/")
        assert rsp.status_code == 200, "Response code is %s" % rsp.status_code

        # Get non existent movie from list
        rsp = api_client.get("/movie_list/1/movies/1/")
        assert rsp.status_code == 404, "Response code is %s" % rsp.status_code

        # Delete non existent movie from list
        rsp = api_client.delete("/movie_list/1/movies/1/")
        assert rsp.status_code == 404, "Response code is %s" % rsp.status_code
    def test_percent(self, api_client, schema_match):
        payload1 = {'percent': '79%'}

        rsp = api_client.json_post('/format_check/', data=json.dumps(payload1))
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors

        payload2 = {'percent': 'bla'}

        rsp = api_client.json_post('/format_check/', data=json.dumps(payload2))
        assert rsp.status_code == 422, 'Response code is %s' % rsp.status_code
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors
    def send_push(self, task, auth_token, room_key, color, notify, title, message, url):

        body = '%s %s' % (title, message)

        data = {'color': color, 'message': body, 'notify': notify, 'message_format': "text"}

        # Check for test mode
        if task.options.test:
            log.info('Test mode. Hipchat notification would be:')
            log.info('      Auth Token: %s' % auth_token)
            log.info('      Room Key: %s' % room_key)
            log.info('      Color: %s' % color)
            log.info('      Notify: %s' % notify)
            log.info('      Title: %s' % title)
            log.info('      Message: %s' % message)
            log.info('      URL: %s' % url)
            log.info('      Raw Data: %s' % json.dumps(data))
            # Test mode.  Skip remainder.
            return

        # Make the request
        headers = {
            'Content-Type': 'application/json'
        }
        response = task.requests.post(url, headers=headers, data=json.dumps(data), raise_status=False)

        # Check if it succeeded
        request_status = response.status_code

        # error codes and messages from Hipchat API
        if request_status == 200:
            log.debug('Hipchat notification sent')
        elif request_status == 500:
            log.warning('Hipchat notification failed, Hipchat API having issues')
            # TODO: Implement retrying. API requests 5 seconds between retries.
        elif request_status >= 400:
            if response.content:
                try:
                    error = json.loads(response.content)['error']
                except ValueError:
                    error = 'Unknown Error (Invalid JSON returned)'
            log.error('Hipchat API error: %s' % error['message'])
        else:
            log.error('Unknown error when sending Hipchat notification')
Beispiel #30
0
    def test_new_series_begin(self, execute_task, api_client):
        show = 'Test Show'
        new_show = {
            "series_name": show,
            "episode_identifier": "s01e01",
            "alternate_names": ['show1', 'show2']
        }

        rsp = api_client.json_post(('/series/'), data=json.dumps(new_show))
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code
Beispiel #31
0
    def on_task_input(self, task, config):
        # Don't edit the config, or it won't pass validation on rerun
        url_params = config.copy()
        if 'movies' in config and 'series' in config:
            raise plugin.PluginError('Cannot use both series list and movies list in the same task.')
        if 'movies' in config:
            url_params['data_type'] = 'movies'
            url_params['list_type'] = config['movies']
            map = self.movie_map
        elif 'series' in config:
            url_params['data_type'] = 'shows'
            url_params['list_type'] = config['series']
            map = self.series_map
        elif 'custom' in config:
            url_params['data_type'] = 'custom'
            url_params['list_type'] = make_list_slug(config['custom'])
            # Map type is per item in custom lists
        else:
            raise plugin.PluginError('Must define movie or series lists to retrieve from trakt.')

        url = 'http://api.trakt.tv/user/'
        auth = None
        if url_params['data_type'] == 'custom':
            url += 'list.json/%(api_key)s/%(username)s/%(list_type)s'
        elif url_params['list_type'] == 'watchlist':
            url += 'watchlist/%(data_type)s.json/%(api_key)s/%(username)s'
        else:
            url += 'library/%(data_type)s/%(list_type)s.json/%(api_key)s/%(username)s'
        url = url % url_params
        
        if 'password' in config:
            auth = {'username': config['username'],
                    'password': hashlib.sha1(config['password']).hexdigest()}

        entries = []
        log.verbose('Retrieving list %s %s...' % (url_params['data_type'], url_params['list_type']))

        try:
            result = task.requests.post(url, data=json.dumps(auth))
        except RequestException as e:
            raise plugin.PluginError('Could not retrieve list from trakt (%s)' % e.message)
        try:
            data = result.json()
        except ValueError:
            log.debug('Could not decode json from response: %s', data.text)
            raise plugin.PluginError('Error getting list from trakt.')

        def check_auth():
            if task.requests.post(
                    'http://api.trakt.tv/account/test/' + config['api_key'],
                    data=json.dumps(auth), raise_status=False
            ).status_code != 200:
                raise plugin.PluginError('Authentication to trakt failed.')

        if 'error' in data:
            check_auth()
            raise plugin.PluginError('Error getting trakt list: %s' % data['error'])
        if not data:
            check_auth()
            log.warning('No data returned from trakt.')
            return
        if url_params['data_type'] == 'custom':
            if not isinstance(data['items'], list):
                raise plugin.PluginError('Faulty custom items in response: %s' % data['items'])
            data = data['items']
        for item in data:
            entry = Entry()
            if url_params['data_type'] == 'custom':
                if 'rating' in item:
                    entry['trakt_in_collection'] = item['in_collection']
                    entry['trakt_in_watchlist'] = item['in_watchlist']
                    entry['trakt_rating'] = item['rating']
                    entry['trakt_rating_advanced'] = item['rating_advanced']
                    entry['trakt_watched'] = item['watched']
                if item['type'] == 'movie':
                    map = self.movie_map
                    item = item['movie']
                else:
                    map = self.series_map
                    item = item['show']
            entry.update_using_map(map, item)
            if entry.isvalid():
                if config.get('strip_dates'):
                    # Remove year from end of name if present
                    entry['title'] = re.sub('\s+\(\d{4}\)$', '', entry['title'])
                entries.append(entry)

        return entries
Beispiel #32
0
    def on_task_output(self, task, config):
        """Finds accepted movies and series episodes and submits them to trakt as acquired."""
        # Change password to an SHA1 digest of the password
        config['password'] = hashlib.sha1(config['password']).hexdigest()
        found = {}
        for entry in task.accepted:
            if config['type'] == 'series':
                # Check entry is a series episode
                if entry.get('series_name') and entry.get(
                        'series_id_type') == 'ep':
                    series = found.setdefault(entry['series_name'], {})
                    if not series:
                        # If this is the first episode found from this series, set the parameters
                        series['title'] = entry.get('tvdb_series_name',
                                                    entry['series_name'])
                        if entry.get('imdb_id'):
                            series['imdb_id'] = entry['imdb_id']
                        if entry.get('tvdb_id'):
                            series['tvdb_id'] = entry['tvdb_id']
                        series['episodes'] = []
                    episode = {
                        'season': entry['series_season'],
                        'episode': entry['series_episode']
                    }
                    series['episodes'].append(episode)
                    log.debug(
                        'Marking %s S%02dE%02d for submission to trakt.tv library.'
                        % (entry['series_name'], entry['series_season'],
                           entry['series_episode']))
            else:
                # Check entry is a movie
                if entry.get('imdb_id') or entry.get('tmdb_id'):
                    movie = {}
                    # We know imdb_id or tmdb_id is filled in, so don't cause any more lazy lookups
                    if entry.get('movie_name', eval_lazy=False):
                        movie['title'] = entry['movie_name']
                    if entry.get('movie_year', eval_lazy=False):
                        movie['year'] = entry['movie_year']
                    if entry.get('tmdb_id', eval_lazy=False):
                        movie['tmdb_id'] = entry['tmdb_id']
                    if entry.get('imdb_id', eval_lazy=False):
                        movie['imdb_id'] = entry['imdb_id']
                    # We use an extra container dict so that the found dict is usable in the same way as found series
                    found.setdefault('movies', {}).setdefault('movies',
                                                              []).append(movie)
                    log.debug(
                        'Marking %s for submission to trakt.tv library.' %
                        entry['title'])

        if not found:
            log.debug('Nothing to submit to trakt.')
            return

        if task.options.test:
            log.info('Not submitting to trakt.tv because of test mode.')
            return

        # Submit our found items to trakt
        if config['type'] == 'series':
            post_url = 'http://api.trakt.tv/show/episode/library/' + config[
                'api_key']
        else:
            post_url = 'http://api.trakt.tv/movie/library/' + config['api_key']
        for item in found.itervalues():
            # Add username and password to the dict to submit
            item.update({
                'username': config['username'],
                'password': config['password']
            })
            try:
                result = task.requests.post(post_url,
                                            data=json.dumps(item),
                                            raise_status=False)
            except RequestException as e:
                log.error('Error submitting data to trakt.tv: %s' % e)
                continue

            if result.status_code == 404:
                # Remove some info from posted json and print the rest to aid debugging
                for key in ['username', 'password', 'episodes']:
                    item.pop(key, None)
                log.warning('%s not found on trakt: %s' %
                            (config['type'].capitalize(), item))
                continue
            elif result.status_code == 401:
                log.error(
                    'Error authenticating with trakt. Check your username/password/api_key'
                )
                log.debug(result.text)
                continue
            elif result.status_code != 200:
                log.error('Error submitting data to trakt.tv: %s' %
                          result.text)
                continue
Beispiel #33
0
    def test_movie_list_movie(self, api_client, schema_match):
        payload = {'name': 'name'}

        # Create list
        rsp = api_client.json_post('/movie_list/', data=json.dumps(payload))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code

        identifier = {'imdb_id': 'tt1234567'}
        movie_data = {'movie_name': 'title', 'movie_identifiers': [identifier]}

        # Add movie to list
        rsp = api_client.json_post('/movie_list/1/movies/',
                                   data=json.dumps(movie_data))
        assert rsp.status_code == 201, 'Response code is %s' % rsp.status_code

        # Get specific movie from list
        rsp = api_client.get('/movie_list/1/movies/1/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.movie_list_object, data)
        assert not errors

        returned_identifier = data['movies_list_ids'][0]
        assert returned_identifier['id_name'], returned_identifier[
            'id_value'] == identifier.items()[0]

        identifiers = [{'trakt_movie_id': '12345'}]

        # Change specific movie from list
        rsp = api_client.json_put('/movie_list/1/movies/1/',
                                  data=json.dumps(identifiers))
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(OC.movie_list_object, data)
        assert not errors

        returned_identifier = data['movies_list_ids'][0]
        assert returned_identifier['id_name'], returned_identifier[
            'id_value'] == identifiers[0].items()

        # PUT non-existent movie from list
        rsp = api_client.json_put('/movie_list/1/movies/10/',
                                  data=json.dumps(identifiers))
        assert rsp.status_code == 404, 'Response code is %s' % rsp.status_code

        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(base_message, data)
        assert not errors

        non_valid_identifier = [{'bla': 'tt1234567'}]
        # Change movie using invalid identifier from list
        rsp = api_client.json_put('/movie_list/1/movies/1/',
                                  data=json.dumps(non_valid_identifier))
        assert rsp.status_code == 400, 'Response code is %s' % rsp.status_code

        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match(base_message, data)
        assert not errors

        # Delete specific movie from list
        rsp = api_client.delete('/movie_list/1/movies/1/')
        assert rsp.status_code == 200, 'Response code is %s' % rsp.status_code

        data = json.loads(rsp.get_data(as_text=True))
        errors = schema_match({'type': 'object'}, data)
        assert not errors

        # Get non existent movie from list
        rsp = api_client.get('/movie_list/1/movies/1/')
        assert rsp.status_code == 404, 'Response code is %s' % rsp.status_code

        # Delete non existent movie from list
        rsp = api_client.delete('/movie_list/1/movies/1/')
        assert rsp.status_code == 404, 'Response code is %s' % rsp.status_code
Beispiel #34
0
    def add_entries(self, task, config):
        """Adds accepted entries"""

        apiurl = config.get('api', self.DEFAULT_API)
        api = PyloadApi(task.requests, apiurl)

        try:
            session = api.get_session(config)
        except OSError:
            raise plugin.PluginError('pyLoad not reachable', logger)
        except plugin.PluginError:
            raise
        except Exception as e:
            raise plugin.PluginError('Unknown error: %s' % str(e), logger)

        remote_version = None
        try:
            remote_version = api.get('getServerVersion')
        except RequestException as e:
            if e.response is not None and e.response.status_code == 404:
                remote_version = json.loads(
                    api.get('get_server_version').content)
            else:
                raise e

        parse_urls_command = 'parseURLs'
        add_package_command = 'addPackage'
        set_package_data_command = 'setPackageData'

        is_pyload_ng = False
        version = self.get_version_from_packaging()
        if version and version.parse(remote_version) >= version.parse('0.5'):
            parse_urls_command = 'parse_urls'
            add_package_command = 'add_package'
            set_package_data_command = 'set_package_date'
            is_pyload_ng = True

        hoster = config.get('hoster', self.DEFAULT_HOSTER)

        for entry in task.accepted:
            # bunch of urls now going to check
            content = entry.get('description', '') + ' ' + quote(entry['url'])
            content = json.dumps(content)

            if is_pyload_ng:
                url = (entry['url'] if config.get(
                    'parse_url', self.DEFAULT_PARSE_URL) else '')
            else:
                url = (json.dumps(entry['url']) if config.get(
                    'parse_url', self.DEFAULT_PARSE_URL) else "''")

            logger.debug('Parsing url {}', url)

            data = {'html': content, 'url': url}
            if not is_pyload_ng:
                data['session'] = session
            result = api.post(parse_urls_command, data=data)

            parsed = result.json()

            urls = []

            # check for preferred hoster
            for name in hoster:
                if name in parsed:
                    urls.extend(parsed[name])
                    if not config.get('multiple_hoster',
                                      self.DEFAULT_MULTIPLE_HOSTER):
                        break

            # no preferred hoster and not preferred hoster only - add all recognized plugins
            if not urls and not config.get('preferred_hoster_only',
                                           self.DEFAULT_PREFERRED_HOSTER_ONLY):
                for name, purls in parsed.items():
                    if name != 'BasePlugin':
                        urls.extend(purls)

            if task.options.test:
                logger.info('Would add `{}` to pyload', urls)
                continue

            # no urls found
            if not urls:
                if config.get('handle_no_url_as_failure',
                              self.DEFAULT_HANDLE_NO_URL_AS_FAILURE):
                    entry.fail('No suited urls in entry %s' % entry['title'])
                else:
                    logger.info('No suited urls in entry {}', entry['title'])
                continue

            logger.debug('Add {} urls to pyLoad', len(urls))

            try:
                dest = 1 if config.get(
                    'queue',
                    self.DEFAULT_QUEUE) else 0  # Destination.Queue = 1

                # Use the title of the entry, if no naming schema for the package is defined.
                name = config.get('package', entry['title'])

                # If name has jinja template, render it
                try:
                    name = entry.render(name)
                except RenderError as e:
                    name = entry['title']
                    logger.error('Error rendering jinja event: {}', e)

                if is_pyload_ng:
                    data = {
                        'name': name.encode('ascii', 'ignore').decode(),
                        'links': urls,
                        'dest': dest,
                    }
                else:
                    data = {
                        'name':
                        json.dumps(name.encode('ascii', 'ignore').decode()),
                        'links': json.dumps(urls),
                        'dest': json.dumps(dest),
                        'session': session
                    }

                pid = api.post(add_package_command, data=data).text
                logger.debug('added package pid: {}', pid)

                # Set Folder
                folder = config.get('folder', self.DEFAULT_FOLDER)
                folder = entry.get('path', folder)
                if folder:
                    # If folder has jinja template, render it
                    try:
                        folder = entry.render(folder)
                    except RenderError as e:
                        folder = self.DEFAULT_FOLDER
                        logger.error('Error rendering jinja event: {}', e)
                    # set folder with api
                    data = json.dumps({'folder': folder})
                    post_data = {'pid': pid, 'data': data}
                    if not is_pyload_ng:
                        post_data['session'] = session
                    api.post(set_package_data_command, data=post_data)

                # Set Package Password
                package_password = config.get('package_password')
                if package_password:
                    data = json.dumps({'password': package_password})
                    post_data = {'pid': pid, 'data': data}
                    if not is_pyload_ng:
                        post_data['session'] = session
                    api.post(set_package_data_command, data=post_data)

            except Exception as e:
                entry.fail(str(e))
Beispiel #35
0
 def test_json_encode_dt(self):
     date_str = '2016-03-11T17:12:17Z'
     dt = datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%SZ')
     encoded_dt = json.dumps(dt, encode_datetime=True)
     assert encoded_dt == '"%s"' % date_str
    def on_task_metainfo(self, task, config):
        if not task.entries:
            return
        url = 'http://api.trakt.tv/user/library/shows/collection.json/%s/%s' % \
            (config['api_key'], config['username'])
        auth = None
        if 'password' in config:
            auth = {
                'username': config['username'],
                'password': hashlib.sha1(config['password']).hexdigest()
            }
        try:
            log.debug('Opening %s' % url)
            data = task.requests.get(url, data=json.dumps(auth)).json()
        except RequestException as e:
            raise plugin.PluginError('Unable to get data from trakt.tv: %s' %
                                     e)

        def check_auth():
            if task.requests.post(
                    'http://api.trakt.tv/account/test/' + config['api_key'],
                    data=json.dumps(auth),
                    raise_status=False).status_code != 200:
                raise plugin.PluginError('Authentication to trakt failed.')

        if not data:
            check_auth()
            self.log.warning('No data returned from trakt.')
            return
        if 'error' in data:
            check_auth()
            raise plugin.PluginError('Error getting trakt list: %s' %
                                     data['error'])
        log.verbose('Received %d series records from trakt.tv' % len(data))
        # the index will speed the work if we have a lot of entries to check
        index = {}
        for idx, val in enumerate(data):
            index[val['title']] = index[int(
                val['tvdb_id'])] = index[val['imdb_id']] = idx
        for entry in task.entries:
            if not (entry.get('series_name') and entry.get('series_season')
                    and entry.get('series_episode')):
                continue
            entry['trakt_in_collection'] = False
            if 'tvdb_id' in entry and entry['tvdb_id'] in index:
                series = data[index[entry['tvdb_id']]]
            elif 'imdb_id' in entry and entry['imdb_id'] in index:
                series = data[index[entry['imdb_id']]]
            elif 'series_name' in entry and entry['series_name'] in index:
                series = data[index[entry['series_name']]]
            else:
                continue
            for s in series['seasons']:
                if s['season'] == entry['series_season']:
                    entry['trakt_in_collection'] = entry[
                        'series_episode'] in s['episodes']
                    break
            log.debug(
                'The result for entry "%s" is: %s' %
                (entry['title'],
                 'Owned' if entry['trakt_in_collection'] else 'Not owned'))
Beispiel #37
0
    def process_notifications(self, task, entries, config):
        for entry in entries:
            if task.options.test:
                log.info("Would send RapidPush notification about: %s", entry['title'])
                continue

            log.info("Send RapidPush notification about: %s", entry['title'])
            apikey = entry.get('apikey', config['apikey'])
            if isinstance(apikey, list):
                apikey = ','.join(apikey)

            title = config['title']
            try:
                title = entry.render(title)
            except RenderError as e:
                log.error('Error setting RapidPush title: %s' % e)

            message = config['message']
            try:
                message = entry.render(message)
            except RenderError as e:
                log.error('Error setting RapidPush message: %s' % e)

            # Check if we have to send a normal or a broadcast notification.
            if not config['channel']:
                priority = entry.get('priority', config['priority'])

                category = entry.get('category', config['category'])
                try:
                    category = entry.render(category)
                except RenderError as e:
                    log.error('Error setting RapidPush category: %s' % e)

                group = entry.get('group', config['group'])
                try:
                    group = entry.render(group)
                except RenderError as e:
                    log.error('Error setting RapidPush group: %s' % e)

                # Send the request
                data_string = json.dumps({
                    'title': title,
                    'message': message,
                    'priority': priority,
                    'category': category,
                    'group': group})
                data = {'apikey': apikey, 'command': 'notify', 'data': data_string}
            else:
                channel = config['channel']
                try:
                    channel = entry.render(channel)
                except RenderError as e:
                    log.error('Error setting RapidPush channel: %s' % e)

                # Send the broadcast request
                data_string = json.dumps({
                    'title': title,
                    'message': message,
                    'channel': channel})
                data = {'apikey': apikey, 'command': 'broadcast', 'data': data_string}

            response = task.requests.post(url, data=data, raise_status=False)

            json_data = response.json()
            if 'code' in json_data:
                if json_data['code'] == 200:
                    log.debug("RapidPush message sent")
                else:
                    log.error(json_data['desc'] + " (" + str(json_data['code']) + ")")
            else:
                for item in json_data:
                    if json_data[item]['code'] == 200:
                        log.debug(item + ": RapidPush message sent")
                    else:
                        log.error(item + ": " + json_data[item]['desc'] + " (" + str(json_data[item]['code']) + ")")
Beispiel #38
0
    def on_task_output(self, task, config):
        """Finds accepted movies and series episodes and submits them to trakt as acquired."""
        # Change password to an SHA1 digest of the password
        config['password'] = hashlib.sha1(config['password']).hexdigest()

        # Don't edit the config, or it won't pass validation on rerun
        url_params = config.copy()
        url_params['data_type'] = 'list'
        # Do some translation from visible list name to prepare for use in url
        list_name = config['list'].lower()
        # These characters are just stripped in the url
        for char in '!@#$%^*()[]{}/=?+\\|-_':
            list_name = list_name.replace(char, '')
        # These characters get replaced
        list_name = list_name.replace('&', 'and')
        list_name = list_name.replace(' ', '-')
        url_params['list_type'] = list_name
        # Map type is per item in custom lists

        found = {}
        for entry in task.accepted:
            # if config['type'] == 'series':
            # # Check entry is a series episode
            # if entry.get('series_name') and entry.get('series_id_type') == 'ep':
            # series = found.setdefault(entry['series_name'], {})
            # if not series:
            # # If this is the first episode found from this series, set the parameters
            # series['title'] = entry.get('tvdb_series_name', entry['series_name'])
            # if entry.get('imdb_id'):
            # series['imdb_id'] = entry['imdb_id']
            # if entry.get('tvdb_id'):
            # series['tvdb_id'] = entry['tvdb_id']
            # series['episodes'] = []
            # episode = {'season': entry['series_season'], 'episode': entry['series_episode']}
            # series['episodes'].append(episode)
            # log.debug('Marking %s S%02dE%02d for submission to trakt.tv library.' %
            # (entry['series_name'], entry['series_season'], entry['series_episode']))
            if config['type'] == 'movies':
                # Check entry is a movie
                if entry.get('imdb_id') or entry.get('tmdb_id'):
                    movie = {}
                    # We know imdb_id or tmdb_id is filled in, so don't cause any more lazy lookups
                    if entry.get('movie_name', eval_lazy=False):
                        movie['title'] = entry['movie_name']
                    if entry.get('movie_year', eval_lazy=False):
                        movie['year'] = entry['movie_year']
                    if entry.get('tmdb_id', eval_lazy=False):
                        movie['tmdb_id'] = entry['tmdb_id']
                    if entry.get('imdb_id', eval_lazy=False):
                        movie['imdb_id'] = entry['imdb_id']
                    # We use an extra container dict so that the found dict is usable in the same way as found series
                    if url_params['list'] == 'watchlist':
                        found.setdefault('movies',
                                         {}).setdefault('movies',
                                                        []).append(movie)
                    else:
                        movie['type'] = 'movie'
                        found.setdefault('items',
                                         {}).setdefault('items',
                                                        []).append(movie)
                    log.debug(
                        'Marking %s for submission to trakt.tv library.' %
                        entry['title'])
                    # log.verbose('json dump (found) : %s' % json.dumps(found))

        if not found:
            log.debug('Nothing to submit to trakt.')
            return

        if task.manager.options.test:
            log.info('Not submitting to trakt.tv because of test mode.')
            return

        # URL to remove collected entries from trakt list
        if url_params['list'] == 'watchlist':
            post_url = 'http://api.trakt.tv/movie/unwatchlist/' + config[
                'api_key']
        else:
            post_url = 'http://api.trakt.tv/lists/items/delete/' + config[
                'api_key']

        # Delete entry from list
        for item in found.itervalues():
            # Add username, password and list (slug) to the dict to submit
            item.update({
                'username': config['username'],
                'password': config['password'],
                'slug': url_params['list']
            })
            try:
                result = task.requests.post(post_url,
                                            data=json.dumps(item),
                                            raise_status=False)
            except RequestException as e:
                log.error('Error submitting data to trakt.tv: %s' % e)
                continue

            if result.status_code == 404:
                # Remove some info from posted json and print the rest to aid debugging
                for key in ['username', 'password', 'episodes']:
                    item.pop(key, None)
                log.warning('%s not found on trakt (remove_collected): %s' %
                            (config['type'].capitalize(), item))
                continue
            elif result.status_code == 401:
                log.error(
                    'Error authenticating with trakt (remove_collected). Check your username/password/api_key'
                )
                log.debug(result.text)
                continue
            elif result.status_code != 200:
                log.error(
                    'Error submitting data to trakt.tv (remove_collected): %s'
                    % result.text)
                continue
Beispiel #39
0
 def write(self, s):
     self.put(json.dumps({'log': s}))
Beispiel #40
0
    def on_task_output(self, task, config):
        """Submits accepted movies or episodes to trakt api."""
        found = {'shows': [], 'movies': []}
        for entry in task.accepted:
            if 'series_name' in entry:
                show = {
                    'title': entry['series_name'],
                    'ids': get_entry_ids(entry)
                }
                if 'series_season' in entry:
                    season = {'number': entry['series_season']}
                    if 'series_episode' in entry:
                        season['episodes'] = [{
                            'number': entry['series_episode']
                        }]
                    show['seasons'] = [season]
                found['shows'].append(show)
            elif any(field in entry
                     for field in ['imdb_id', 'tmdb_id', 'movie_name']):
                movie = {'ids': get_entry_ids(entry)}
                if not movie['ids']:
                    movie['title'] = entry.get('movie_name') or entry.get(
                        'imdb_name')
                    movie['year'] = entry.get('movie_year') or entry.get(
                        'imdb_year')
                found['movies'].append(movie)

        if not (found['shows'] or found['movies']):
            self.log.debug('Nothing to submit to trakt.')
            return

        if config['list'] in ['collection', 'watchlist', 'watched']:
            endpoint = 'sync/%s' % ('history' if config['list'] == 'watched'
                                    else config['list'])
        else:
            endpoint = 'users/%s/lists/%s/items' % (
                config['username'], make_list_slug(config['list']))
        if self.remove:
            endpoint += '/remove'
        url = API_URL + endpoint

        if task.manager.options.test:
            self.log.info('Not submitting to trakt.tv because of test mode.')
            return
        session = get_session(config['username'], config['password'])
        self.log.debug('Submitting data to trakt.tv (%s): %s' % (url, found))
        try:
            result = session.post(url,
                                  data=json.dumps(found),
                                  raise_status=False)
        except RequestException as e:
            self.log.error('Error submitting data to trakt.tv: %s' % e)
            return
        if 200 <= result.status_code < 300:
            self.log.info('Data successfully sent to trakt.tv')
            self.log.debug('trakt response: ' + result.text)
            # TODO: Improve messages about existing and unknown results
        elif result.status_code == 404:
            self.log.error('List does not appear to exist on trakt: %s' %
                           config['list'])
        elif result.status_code == 401:
            self.log.error(
                'Authentication error: check your trakt.tv username/password')
            self.log.debug('trakt response: ' + result.text)
        else:
            self.log.error('Unknown error submitting data to trakt.tv: %s' %
                           result.text)
Beispiel #41
0
    def test_pending_list_entry(self, api_client, schema_match):
        payload = {'name': 'test_list'}

        # Create list
        rsp = api_client.json_post('/pending_list/', data=json.dumps(payload))
        assert rsp.status_code == 201
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_list_base_object, data)
        assert not errors

        for field, value in payload.items():
            assert data.get(field) == value

        entry_data = {'title': 'title', 'original_url': 'http://test.com'}

        # Add entry to list
        rsp = api_client.json_post('/pending_list/1/entries/',
                                   data=json.dumps(entry_data))
        assert rsp.status_code == 201
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_list_entry_base_object, data)
        assert not errors

        for field, value in entry_data.items():
            assert data.get(field) == value

        # Get entries from list
        rsp = api_client.get('/pending_list/1/entries/')
        assert rsp.status_code == 200
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_lists_entries_return_object, data)
        assert not errors

        for field, value in entry_data.items():
            assert data[0].get(field) == value

        # Get specific entry from list
        rsp = api_client.get('/pending_list/1/entries/1/')
        assert rsp.status_code == 200
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_list_entry_base_object, data)
        assert not errors

        for field, value in entry_data.items():
            assert data.get(field) == value

        new_entry_data = {'operation': 'approve'}

        # Change specific entry from list
        rsp = api_client.json_put('/pending_list/1/entries/1/',
                                  data=json.dumps(new_entry_data))
        assert rsp.status_code == 201
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_list_entry_base_object, data)
        assert not errors

        assert data['approved']

        # Try to change non-existent entry from list
        rsp = api_client.json_put('/pending_list/1/entries/10/',
                                  data=json.dumps(new_entry_data))
        assert rsp.status_code == 404
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors

        # Delete specific entry from list
        rsp = api_client.delete('/pending_list/1/entries/1/')
        assert rsp.status_code == 200
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors

        # Get non existent entry from list
        rsp = api_client.get('/pending_list/1/entries/1/')
        assert rsp.status_code == 404
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors

        # Delete non existent entry from list
        rsp = api_client.delete('/pending_list/1/entries/1/')
        assert rsp.status_code == 404
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors
Beispiel #42
0
    def send_push(self,
                  task,
                  api_key,
                  title,
                  body,
                  url=None,
                  destination=None,
                  destination_type=None):

        if url:
            push_type = 'link'
        else:
            push_type = 'note'

        data = {'type': push_type, 'title': title, 'body': body}
        if url:
            data['url'] = url
        if destination:
            data[destination_type] = destination

        # Check for test mode
        if task.options.test:
            log.info('Test mode. Pushbullet notification would be:')
            log.info('    API Key: %s' % api_key)
            log.info('    Type: %s' % push_type)
            log.info('    Title: %s' % title)
            log.info('    Body: %s' % body)
            if destination:
                log.info('    Destination: %s (%s)' %
                         (destination, destination_type))
            if url:
                log.info('    URL: %s' % url)
            log.info('    Raw Data: %s' % json.dumps(data))
            # Test mode.  Skip remainder.
            return

        # Make the request
        headers = {
            'Authorization':
            b'Basic ' + base64.b64encode(api_key.encode('ascii')),
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'User-Agent': 'Flexget'
        }
        response = task.requests.post(pushbullet_url,
                                      headers=headers,
                                      data=json.dumps(data),
                                      raise_status=False)

        # Check if it succeeded
        request_status = response.status_code

        # error codes and messages from Pushbullet API
        if request_status == 200:
            log.debug('Pushbullet notification sent')
        elif request_status == 500:
            log.warning(
                'Pushbullet notification failed, Pushbullet API having issues')
            # TODO: Implement retrying. API requests 5 seconds between retries.
        elif request_status >= 400:
            error = 'Unknown error'
            if response.content:
                try:
                    error = response.json()['error']['message']
                except ValueError as e:
                    error = 'Unknown Error (Invalid JSON returned): %s' % e
            log.error('Pushbullet API error: %s' % error)
        else:
            log.error('Unknown error when sending Pushbullet notification')
Beispiel #43
0
 def test_json_encode_dt_dict(self):
     date_str = '2016-03-11T17:12:17Z'
     dt = datetime.strptime(date_str, '%Y-%m-%dT%H:%M:%SZ')
     date_obj = {'date': dt}
     encoded_dt = json.dumps(date_obj, encode_datetime=True)
     assert encoded_dt == '{"date": "%s"}' % date_str
Beispiel #44
0
    def on_task_input(self, task, config):
        # Don't edit the config, or it won't pass validation on rerun
        url_params = config.copy()
        if 'movies' in config and 'series' in config:
            raise PluginError(
                'Cannot use both series list and movies list in the same task.'
            )
        if 'movies' in config:
            url_params['data_type'] = 'movies'
            url_params['list_type'] = config['movies']
            map = self.movie_map
        elif 'series' in config:
            url_params['data_type'] = 'shows'
            url_params['list_type'] = config['series']
            map = self.series_map
        elif 'custom' in config:
            url_params['data_type'] = 'custom'
            # Do some translation from visible list name to prepare for use in url
            list_name = config['custom'].lower()
            # These characters are just stripped in the url
            for char in '!@#$%^*()[]{}/=?+\\|-_':
                list_name = list_name.replace(char, '')
            # These characters get replaced
            list_name = list_name.replace('&', 'and')
            list_name = list_name.replace(' ', '-')
            url_params['list_type'] = list_name
            # Map type is per item in custom lists
        else:
            raise PluginError(
                'Must define movie or series lists to retrieve from trakt.')

        url = 'http://api.trakt.tv/user/'
        auth = None
        if url_params['data_type'] == 'custom':
            url += 'list.json/%(api_key)s/%(username)s/%(list_type)s'
        elif url_params['list_type'] == 'watchlist':
            url += 'watchlist/%(data_type)s.json/%(api_key)s/%(username)s'
        else:
            url += 'library/%(data_type)s/%(list_type)s.json/%(api_key)s/%(username)s'
        url = url % url_params

        if 'password' in config:
            auth = {
                'username': config['username'],
                'password': hashlib.sha1(config['password']).hexdigest()
            }

        entries = []
        log.verbose('Retrieving list %s %s...' %
                    (url_params['data_type'], url_params['list_type']))

        result = task.requests.get(url, data=json.dumps(auth))
        try:
            data = task.requests.post(url, data=json.dumps(auth)).json()
        except RequestException as e:
            raise PluginError('Could not retrieve list from trakt (%s)' %
                              e.message)

        def check_auth():
            if task.requests.post(
                    'http://api.trakt.tv/account/test/' + config['api_key'],
                    data=json.dumps(auth),
                    raise_status=False).status_code != 200:
                raise PluginError('Authentication to trakt failed.')

        if 'error' in data:
            check_auth()
            raise PluginError('Error getting trakt list: %s' % data['error'])
        if not data:
            check_auth()
            log.warning('No data returned from trakt.')
            return
        if url_params['data_type'] == 'custom':
            if not isinstance(data['items'], list):
                raise PluginError('Faulty custom items in response: %s' %
                                  data['items'])
            data = data['items']
        for item in data:
            if url_params['data_type'] == 'custom':
                if item['type'] == 'movie':
                    map = self.movie_map
                    item = item['movie']
                else:
                    map = self.series_map
                    item = item['show']
            entry = Entry()
            entry.update_using_map(map, item)
            if entry.isvalid():
                if config.get('strip_dates'):
                    # Remove year from end of name if present
                    entry['title'] = re.sub('\s+\(\d{4}\)$', '',
                                            entry['title'])
                entries.append(entry)

        return entries
Beispiel #45
0
 def post_json_to_trakt(self, url, data):
     """Dumps data as json and POSTs it to the specified url."""
     req = urllib2.Request(url, json.dumps(data),
                           {'content-type': 'application/json'})
     return urlopener(req, log)
Beispiel #46
0
    def submit(self, entries, remove=False):
        """Submits movies or episodes to trakt api."""
        found = {}
        for entry in entries:
            if self.config['type'] in ['auto', 'shows', 'seasons', 'episodes'] and entry.get('series_name') is not None:
                show_name, show_year = split_title_year(entry['series_name'])
                show = {'title': show_name, 'ids': get_entry_ids(entry)}
                if show_year:
                    show['year'] = show_year
                if self.config['type'] in ['auto', 'seasons', 'episodes'] and entry.get('series_season') is not None:
                    season = {'number': entry['series_season']}
                    if self.config['type'] in ['auto', 'episodes'] and entry.get('series_episode') is not None:
                        season['episodes'] = [{'number': entry['series_episode']}]
                    show['seasons'] = [season]
                if self.config['type'] in ['seasons', 'episodes'] and 'seasons' not in show:
                    log.debug('Not submitting `%s`, no season found.' % entry['title'])
                    continue
                if self.config['type'] == 'episodes' and 'episodes' not in show:
                    log.debug('Not submitting `%s`, no episode number found.' % entry['title'])
                    continue
                found.setdefault('shows', []).append(show)
            elif self.config['type'] in ['auto', 'movies']:
                movie = {'ids': get_entry_ids(entry)}
                if not movie['ids']:
                    if entry.get('movie_name') is not None:
                        movie['title'] = entry.get('movie_name') or entry.get('imdb_name')
                        movie['year'] = entry.get('movie_year') or entry.get('imdb_year')
                    else:
                        log.debug('Not submitting `%s`, no movie name or id found.' % entry['title'])
                        continue
                found.setdefault('movies', []).append(movie)

        if not (found.get('shows') or found.get('movies')):
            log.debug('Nothing to submit to trakt.')
            return

        if self.config['list'] in ['collection', 'watchlist', 'watched']:
            args = ('sync', 'history' if self.config['list'] == 'watched' else self.config['list'])
        else:
            args = ('users', self.config['username'], 'lists', make_list_slug(self.config['list']), 'items')
        if remove:
            args += ('remove',)
        url = get_api_url(args)

        log.debug('Submitting data to trakt.tv (%s): %s' % (url, found))
        try:
            result = self.session.post(url, data=json.dumps(found), raise_status=False)
        except RequestException as e:
            log.error('Error submitting data to trakt.tv: %s' % e)
            return
        if 200 <= result.status_code < 300:
            action = 'deleted' if remove else 'added'
            res = result.json()
            # Default to 0 for all categories, even if trakt response didn't include them
            for cat in ('movies', 'shows', 'episodes', 'seasons'):
                res[action].setdefault(cat, 0)
            log.info('Successfully {0} to/from list {1}: {movies} movie(s), {shows} show(s), {episodes} episode(s), '
                     '{seasons} season(s).'.format(action, self.config['list'], **res[action]))
            for k, r in res['not_found'].items():
                if r:
                    log.debug('not found %s: %s' % (k, r))
            # TODO: Improve messages about existing and unknown results
            # Mark the results expired if we added or removed anything
            if sum(res[action].values()) > 0:
                self.invalidate_cache()
        elif result.status_code == 404:
            log.error('List does not appear to exist on trakt: %s' % self.config['list'])
        elif result.status_code == 401:
            log.error('Authentication error: have you authorized Flexget on Trakt.tv?')
            log.debug('trakt response: ' + result.text)
        else:
            log.error('Unknown error submitting data to trakt.tv: %s' % result.text)
Beispiel #47
0
 def check_auth():
     if task.requests.post(
             'http://api.trakt.tv/account/test/' + config['api_key'],
             data=json.dumps(auth), raise_status=False
     ).status_code != 200:
         raise plugin.PluginError('Authentication to trakt failed.')
Beispiel #48
0
    def add_entries(self, task, config):
        """Adds accepted entries"""

        try:
            session = self.get_session(config)
        except IOError:
            raise plugin.PluginError('pyLoad not reachable', log)
        except plugin.PluginError:
            raise
        except Exception as e:
            raise plugin.PluginError('Unknown error: %s' % str(e), log)

        api = config.get('api', self.DEFAULT_API)
        hoster = config.get('hoster', self.DEFAULT_HOSTER)

        for entry in task.accepted:
            # bunch of urls now going to check
            content = entry.get('description', '') + ' ' + quote(entry['url'])
            content = json.dumps(content.encode("utf8"))

            url = json.dumps(entry['url']) if config.get(
                'parse_url', self.DEFAULT_PARSE_URL) else "''"

            log.debug("Parsing url %s" % url)

            result = query_api(api, "parseURLs", {
                "html": content,
                "url": url,
                "session": session
            })

            # parsed { plugins: [urls] }
            parsed = result.json()

            urls = []

            # check for preferred hoster
            for name in hoster:
                if name in parsed:
                    urls.extend(parsed[name])
                    if not config.get('multiple_hoster',
                                      self.DEFAULT_MULTIPLE_HOSTER):
                        break

            # no preferred hoster and not preferred hoster only - add all recognized plugins
            if not urls and not config.get('preferred_hoster_only',
                                           self.DEFAULT_PREFERRED_HOSTER_ONLY):
                for name, purls in parsed.iteritems():
                    if name != "BasePlugin":
                        urls.extend(purls)

            if task.options.test:
                log.info('Would add `%s` to pyload' % urls)
                continue

            # no urls found
            if not urls:
                if config.get('handle_no_url_as_failure',
                              self.DEFAULT_HANDLE_NO_URL_AS_FAILURE):
                    entry.fail("No suited urls in entry %s" % entry['title'])
                else:
                    log.info("No suited urls in entry %s" % entry['title'])
                continue

            log.debug("Add %d urls to pyLoad" % len(urls))

            try:
                dest = 1 if config.get(
                    'queue',
                    self.DEFAULT_QUEUE) else 0  # Destination.Queue = 1

                # Use the title of the entry, if no naming schema for the package is defined.
                name = config.get('package', entry['title'])

                # If name has jinja template, render it
                try:
                    name = entry.render(name)
                except RenderError as e:
                    name = entry['title']
                    log.error('Error rendering jinja event: %s' % e)

                post = {
                    'name': "'%s'" % name.encode("ascii", "ignore"),
                    'links': str(urls),
                    'dest': dest,
                    'session': session
                }

                pid = query_api(api, "addPackage", post).text
                log.debug('added package pid: %s' % pid)

                # Set Folder
                folder = config.get('folder', self.DEFAULT_FOLDER)
                folder = entry.get('path', folder)
                if folder:
                    # If folder has jinja template, render it
                    try:
                        folder = entry.render(folder)
                    except RenderError as e:
                        folder = self.DEFAULT_FOLDER
                        log.error('Error rendering jinja event: %s' % e)
                    # set folder with api
                    data = json.dumps({'folder': folder})
                    query_api(api, "setPackageData", {
                        'pid': pid,
                        'data': data,
                        'session': session
                    })

            except Exception as e:
                entry.fail(str(e))
 def process(self):
     imdb_lookup = plugin.get_plugin_by_name('imdb_lookup').instance
     self.changes.sort()
     udata = load_uoccin_data(self.folder)
     for line in self.changes:
         tmp = line.split('|')
         typ = tmp[1]
         tid = tmp[2]
         fld = tmp[3]
         val = tmp[4]
         self.log.verbose(
             'processing: type=%s, target=%s, field=%s, value=%s' %
             (typ, tid, fld, val))
         if typ == 'movie':
             # default
             mov = udata['movies'].setdefault(
                 tid, {
                     'name': 'N/A',
                     'watchlist': False,
                     'collected': False,
                     'watched': False
                 })
             # movie title is unknown at this time
             fake = Entry()
             fake['url'] = 'http://www.imdb.com/title/' + tid
             fake['imdb_id'] = tid
             try:
                 imdb_lookup.lookup(fake)
                 mov['name'] = fake.get('imdb_name')
             except plugin.PluginError:
                 self.log.warning(
                     'Unable to lookup movie %s from imdb, using raw name.'
                     % tid)
             # setting
             if fld == 'watchlist':
                 mov['watchlist'] = val == 'true'
             elif fld == 'collected':
                 mov['collected'] = val == 'true'
             elif fld == 'watched':
                 mov['watched'] = val == 'true'
             elif fld == 'tags':
                 mov['tags'] = re.split(',\s*', val)
             elif fld == 'subtitles':
                 mov['subtitles'] = re.split(',\s*', val)
             elif fld == 'rating':
                 mov['rating'] = int(val)
             # cleaning
             if not (mov['watchlist'] or mov['collected']
                     or mov['watched']):
                 self.log.verbose('deleting unused section: movies\%s' %
                                  tid)
                 udata['movies'].pop(tid)
         elif typ == 'series':
             tmp = tid.split('.')
             sid = tmp[0]
             sno = tmp[1] if len(tmp) > 2 else None
             eno = tmp[2] if len(tmp) > 2 else None
             # default
             ser = udata['series'].setdefault(
                 sid, {
                     'name': 'N/A',
                     'watchlist': False,
                     'collected': {},
                     'watched': {}
                 })
             # series name is unknown at this time
             try:
                 series = lookup_series(tvdb_id=sid)
                 ser['name'] = series.name
             except LookupError:
                 self.log.warning(
                     'Unable to lookup series %s from tvdb, using raw name.'
                     % sid)
             # setting
             if fld == 'watchlist':
                 ser['watchlist'] = val == 'true'
             elif fld == 'tags':
                 ser['tags'] = re.split(',\s*', val)
             elif fld == 'rating':
                 ser['rating'] = int(val)
             elif sno is None or eno is None:
                 self.log.warning(
                     'invalid line "%s": season and episode numbers are required'
                     % line)
             elif fld == 'collected':
                 season = ser['collected'].setdefault(sno, {})
                 if val == 'true':
                     season.setdefault(eno, [])
                 else:
                     if eno in season:
                         season.pop(eno)
                     if not season:
                         self.log.verbose(
                             'deleting unused section: series\%s\collected\%s'
                             % (sid, sno))
                         ser['collected'].pop(sno)
             elif fld == 'subtitles':
                 ser['collected'].setdefault(sno, {})[eno] = re.split(
                     ',\s*', val)
             elif fld == 'watched':
                 season = ser['watched'].setdefault(sno, [])
                 if val == 'true':
                     season = ser['watched'][sno] = list(
                         set(season) | set([int(eno)]))
                 elif int(eno) in season:
                     season.remove(int(eno))
                 season.sort()
                 if not season:
                     self.log.debug(
                         'deleting unused section: series\%s\watched\%s' %
                         (sid, sno))
                     ser['watched'].pop(sno)
             # cleaning
             if not (ser['watchlist'] or ser['collected']
                     or ser['watched']):
                 self.log.debug('deleting unused section: series\%s' % sid)
                 udata['series'].pop(sid)
         else:
             self.log.warning('invalid element type "%s"' % typ)
     # save the updated uoccin.json
     ufile = os.path.join(self.folder, 'uoccin.json')
     try:
         text = json.dumps(udata,
                           sort_keys=True,
                           indent=4,
                           separators=(',', ': '))
         with open(ufile, 'w') as f:
             f.write(text)
     except Exception as err:
         self.log.debug('error writing %s: %s' % (ufile, err))
         raise plugin.PluginError('error writing %s: %s' % (ufile, err))
Beispiel #50
0
class PluginPyLoad(object):
    """
    Parse task content or url for hoster links and adds them to pyLoad.

    Example::

      pyload:
        api: http://localhost:8000/api
        queue: yes
        username: my_username
        password: my_password
        folder: desired_folder
        hoster:
          - YoutubeCom
        parse_url: no
        multiple_hoster: yes
        enabled: yes

    Default values for the config elements::

      pyload:
          api: http://localhost:8000/api
          queue: no
          hoster: ALL
          parse_url: no
          multiple_hoster: yes
          enabled: yes
    """

    __author__ = 'http://pyload.org'
    __version__ = '0.3'

    DEFAULT_API = 'http://localhost:8000/api'
    DEFAULT_QUEUE = False
    DEFAULT_FOLDER = ''
    DEFAULT_HOSTER = []
    DEFAULT_PARSE_URL = False
    DEFAULT_MULTIPLE_HOSTER = True

    def __init__(self):
        self.session = None

    def validator(self):
        """Return config validator"""
        root = validator.factory()
        root.accept('boolean')
        advanced = root.accept('dict')
        advanced.accept('text', key='api')
        advanced.accept('text', key='username')
        advanced.accept('text', key='password')
        advanced.accept('text', key='folder')
        advanced.accept('boolean', key='queue')
        advanced.accept('boolean', key='parse_url')
        advanced.accept('boolean', key='multiple_hoster')
        advanced.accept('list', key='hoster').accept('text')
        return root

    def on_process_start(self, task, config):
        self.session = None

    def on_task_output(self, task, config):
        if not config.get('enabled', True):
            return
        if not task.accepted:
            return

        self.add_entries(task, config)

    def add_entries(self, task, config):
        """Adds accepted entries"""

        try:
            self.check_login(task, config)
        except URLError:
            raise PluginError('pyLoad not reachable', log)
        except PluginError:
            raise
        except Exception, e:
            raise PluginError('Unknown error: %s' % str(e), log)

        api = config.get('api', self.DEFAULT_API)
        hoster = config.get('hoster', self.DEFAULT_HOSTER)
        folder = config.get('folder', self.DEFAULT_FOLDER)

        for entry in task.accepted:
            # bunch of urls now going to check
            content = entry.get('description', '') + ' ' + quote(entry['url'])
            content = json.dumps(content.encode("utf8"))

            url = json.dumps(entry['url']) if config.get('parse_url', self.DEFAULT_PARSE_URL) else "''"

            log.debug("Parsing url %s" % url)

            result = query_api(api, "parseURLs", {"html": content, "url": url, "session": self.session})

            # parsed { plugins: [urls] }
            parsed = json.loads(result.read())

            urls = []

            # check for preferred hoster
            for name in hoster:
                if name in parsed:
                    urls.extend(parsed[name])
                    if not config.get('multiple_hoster', self.DEFAULT_MULTIPLE_HOSTER):
                        break

            # no preferred hoster, add all recognized plugins
            if not urls:
                for name, purls in parsed.iteritems():
                    if name != "BasePlugin":
                        urls.extend(purls)

            if task.manager.options.test:
                log.info('Would add `%s` to pyload' % urls)
                continue

            # no urls found
            if not urls:
                log.info("No suited urls in entry %s" % entry['title'])
                continue

            log.debug("Add %d urls to pyLoad" % len(urls))

            try:
                dest = 1 if config.get('queue', self.DEFAULT_QUEUE) else 0  # Destination.Queue = 1
                post = {'name': "'%s'" % entry['title'],
                        'links': str(urls),
                        'dest': dest,
                        'session': self.session}

                pid = query_api(api, "addPackage", post).read()
                log.debug('added package pid: %s' % pid)

                if folder:
                    # set folder with api
                    data = {'folder': folder}
                    query_api(api, "setPackageData", {'pid': pid, 'data': data, 'session': self.session})

            except Exception, e:
                task.fail(entry, str(e))
Beispiel #51
0
def job_id(conf):
    """Create a unique id for a schedule item in config."""
    return hashlib.sha1(json.dumps(conf, sort_keys=True)).hexdigest()
Beispiel #52
0
    def search(self, task, entry, config):
        task.requests.add_domain_limiter(self.request_limiter)
        config = self.prepare_config(config)
        api_key = config['api_key']

        searches = entry.get('search_strings', [entry['title']])

        if 'series_name' in entry:
            if entry.get('season_pack_lookup', False):
                search = {'category': 'Season'}
            else:
                search = {'category': 'Episode'}
            if 'tvdb_id' in entry:
                search['tvdb'] = entry['tvdb_id']
            elif 'tvrage_id' in entry:
                search['tvrage'] = entry['tvrage_id']
            else:
                search['series'] = entry['series_name']
            if entry.get('season_pack_lookup',
                         False) and 'series_season' in entry:
                search['name'] = 'Season %s' % entry['series_season']
            elif 'series_id' in entry:
                # BTN wants an ep style identifier even for sequence shows
                if entry.get('series_id_type') == 'sequence':
                    search['name'] = 'S01E%02d' % entry['series_id']
                else:
                    search['name'] = entry[
                        'series_id'] + '%'  # added wildcard search for better results.
            searches = [search]
            # If searching by series name ending in a parenthetical, try again without it if there are no results.
            if search.get('series') and search['series'].endswith(')'):
                match = re.match('(.+)\([^\(\)]+\)$', search['series'])
                if match:
                    searches.append(dict(search,
                                         series=match.group(1).strip()))

        results = set()
        for search in searches:
            data = json.dumps({
                'method': 'getTorrents',
                'params': [api_key, search],
                'id': 1
            })
            try:
                r = task.requests.post(
                    'https://api.broadcasthe.net/',
                    data=data,
                    headers={'Content-type': 'application/json'})
            except requests.RequestException as e:
                log.error('Error searching btn: %s' % e)
                continue
            try:
                content = r.json()
            except ValueError as e:
                raise plugin.PluginError(
                    'Error searching btn. Maybe it\'s down?. %s' % str(e))
            if not content or not content['result']:
                log.debug('No results from btn')
                if content and content.get('error'):
                    if content['error'].get('code') == -32002:
                        log.error(
                            'btn api call limit exceeded, throttling connection rate'
                        )
                        self.request_limiter.tokens = -1
                    else:
                        log.error(
                            'Error searching btn: %s' %
                            content['error'].get('message', content['error']))
                continue
            if 'torrents' in content['result']:
                for item in content['result']['torrents'].values():
                    entry = Entry()
                    entry['title'] = item['ReleaseName']
                    if config['append_quality']:
                        entry['title'] += ' '.join([
                            '', item['Resolution'], item['Source'],
                            item['Codec']
                        ])
                    entry['url'] = item['DownloadURL']
                    entry['torrent_seeds'] = int(item['Seeders'])
                    entry['torrent_leeches'] = int(item['Leechers'])
                    entry['torrent_info_hash'] = item['InfoHash']
                    entry['search_sort'] = torrent_availability(
                        entry['torrent_seeds'], entry['torrent_leeches'])
                    if item['TvdbID'] and int(item['TvdbID']):
                        entry['tvdb_id'] = int(item['TvdbID'])
                    if item['TvrageID'] and int(item['TvrageID']):
                        entry['tvrage_id'] = int(item['TvrageID'])
                    results.add(entry)
                # Don't continue searching if this search yielded results
                break
        return results
Beispiel #53
0
    def test_pending_list_entries_batch_operation(self, api_client,
                                                  schema_match):
        payload = {'name': 'test_list'}

        # Create list
        api_client.json_post('/pending_list/', data=json.dumps(payload))

        # Add 3 entries to list
        for i in range(3):
            payload = {
                'title': f'title {i}',
                'original_url': f'http://{i}test.com'
            }
            rsp = api_client.json_post('/pending_list/1/entries/',
                                       data=json.dumps(payload))
            assert rsp.status_code == 201

        payload = {'operation': 'approve', 'ids': [1, 2, 3]}

        # Approve several entries
        rsp = api_client.json_put('/pending_list/1/entries/batch/',
                                  data=json.dumps(payload))
        assert rsp.status_code == 201
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_lists_entries_return_object, data)
        assert not errors
        assert len(data) == 3

        assert all((item['approved'] for item in data))

        # get entries is correct
        rsp = api_client.get('/pending_list/1/entries/')
        assert rsp.status_code == 200
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_lists_entries_return_object, data)
        assert not errors
        assert len(data) == 3

        for item in data:
            assert item.get('approved')

        payload['operation'] = 'reject'

        # reject several entries
        rsp = api_client.json_put('pending_list/1/entries/batch',
                                  data=json.dumps(payload))
        assert rsp.status_code == 201
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_lists_entries_return_object, data)
        assert not errors
        assert len(data) == 3

        for item in data:
            assert not item.get('approved')

        rsp = api_client.get('/pending_list/1/entries/')
        assert rsp.status_code == 200
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_lists_entries_return_object, data)
        assert not errors
        assert len(data) == 3

        for item in data:
            assert not item.get('approved')
Beispiel #54
0
    def add_entries(self, task, config):
        """Adds accepted entries"""

        apiurl = config.get('api', self.DEFAULT_API)
        api = PyloadApi(task.requests, apiurl)

        try:
            session = api.get_session(config)
        except IOError:
            raise plugin.PluginError('pyLoad not reachable', log)
        except plugin.PluginError:
            raise
        except Exception as e:
            raise plugin.PluginError('Unknown error: %s' % str(e), log)

        hoster = config.get('hoster', self.DEFAULT_HOSTER)

        for entry in task.accepted:
            # bunch of urls now going to check
            content = entry.get('description', '') + ' ' + quote(entry['url'])
            content = json.dumps(content)

            url = (
                json.dumps(entry['url'])
                if config.get('parse_url', self.DEFAULT_PARSE_URL)
                else "''"
            )

            log.debug('Parsing url %s', url)

            data = {'html': content, 'url': url, 'session': session}
            result = api.post('parseURLs', data=data)

            parsed = result.json()

            urls = []

            # check for preferred hoster
            for name in hoster:
                if name in parsed:
                    urls.extend(parsed[name])
                    if not config.get('multiple_hoster', self.DEFAULT_MULTIPLE_HOSTER):
                        break

            # no preferred hoster and not preferred hoster only - add all recognized plugins
            if not urls and not config.get(
                'preferred_hoster_only', self.DEFAULT_PREFERRED_HOSTER_ONLY
            ):
                for name, purls in parsed.items():
                    if name != 'BasePlugin':
                        urls.extend(purls)

            if task.options.test:
                log.info('Would add `%s` to pyload', urls)
                continue

            # no urls found
            if not urls:
                if config.get('handle_no_url_as_failure', self.DEFAULT_HANDLE_NO_URL_AS_FAILURE):
                    entry.fail('No suited urls in entry %s' % entry['title'])
                else:
                    log.info('No suited urls in entry %s', entry['title'])
                continue

            log.debug('Add %d urls to pyLoad', len(urls))

            try:
                dest = 1 if config.get('queue', self.DEFAULT_QUEUE) else 0  # Destination.Queue = 1

                # Use the title of the entry, if no naming schema for the package is defined.
                name = config.get('package', entry['title'])

                # If name has jinja template, render it
                try:
                    name = entry.render(name)
                except RenderError as e:
                    name = entry['title']
                    log.error('Error rendering jinja event: %s', e)

                data = {
                    'name': json.dumps(name.encode('ascii', 'ignore').decode()),
                    'links': json.dumps(urls),
                    'dest': json.dumps(dest),
                    'session': session,
                }

                pid = api.post('addPackage', data=data).text
                log.debug('added package pid: %s', pid)

                # Set Folder
                folder = config.get('folder', self.DEFAULT_FOLDER)
                folder = entry.get('path', folder)
                if folder:
                    # If folder has jinja template, render it
                    try:
                        folder = entry.render(folder)
                    except RenderError as e:
                        folder = self.DEFAULT_FOLDER
                        log.error('Error rendering jinja event: %s', e)
                    # set folder with api
                    data = json.dumps({'folder': folder})
                    api.post("setPackageData", data={'pid': pid, 'data': data, 'session': session})

                # Set Package Password
                package_password = config.get('package_password')
                if package_password:
                    data = json.dumps({'password': package_password})
                    api.post('setPackageData', data={'pid': pid, 'data': data, 'session': session})

            except Exception as e:
                entry.fail(str(e))
Beispiel #55
0
    def submit(self, entries, remove=False):
        """Submits movies or episodes to trakt api."""
        found = {}
        for entry in entries:
            if self.config['type'] in ['auto', 'shows', 'seasons', 'episodes'] and entry.get(
                'series_name'
            ):
                show_name, show_year = split_title_year(entry['series_name'])
                show = {'title': show_name, 'ids': db.get_entry_ids(entry)}
                if show_year:
                    show['year'] = show_year
                if (
                    self.config['type'] in ['auto', 'seasons', 'episodes']
                    and entry.get('series_season') is not None
                ):
                    season = {'number': entry['series_season']}
                    if (
                        self.config['type'] in ['auto', 'episodes']
                        and entry.get('series_episode') is not None
                    ):
                        season['episodes'] = [{'number': entry['series_episode']}]
                    show['seasons'] = [season]
                if self.config['type'] in ['seasons', 'episodes'] and 'seasons' not in show:
                    logger.debug('Not submitting `{}`, no season found.', entry['title'])
                    continue
                if self.config['type'] == 'episodes' and 'episodes' not in show['seasons'][0]:
                    logger.debug('Not submitting `{}`, no episode number found.', entry['title'])
                    continue
                found.setdefault('shows', []).append(show)
            elif self.config['type'] in ['auto', 'movies']:
                movie = {'ids': db.get_entry_ids(entry)}
                if not movie['ids']:
                    if entry.get('movie_name') is not None:
                        movie['title'] = entry.get('movie_name') or entry.get('imdb_name')
                        movie['year'] = entry.get('movie_year') or entry.get('imdb_year')
                    else:
                        logger.debug(
                            'Not submitting `{}`, no movie name or id found.', entry['title']
                        )
                        continue
                found.setdefault('movies', []).append(movie)

        if not (found.get('shows') or found.get('movies')):
            logger.debug('Nothing to submit to trakt.')
            return

        url = db.get_api_url(self.get_list_endpoint(remove, submit=True))

        logger.debug('Submitting data to trakt.tv ({}): {}', url, found)
        try:
            result = self.session.post(url, data=json.dumps(found), raise_status=False)
        except RequestException as e:
            logger.error('Error submitting data to trakt.tv: {}', e)
            return
        if 200 <= result.status_code < 300:
            action = 'deleted' if remove else 'added'
            res = result.json()
            # Default to 0 for all categories, even if trakt response didn't include them
            for cat in ('movies', 'shows', 'episodes', 'seasons'):
                res[action].setdefault(cat, 0)
            logger.info(
                'Successfully {0} to/from list {1}: {movies} movie(s), {shows} show(s), {episodes} episode(s), '
                '{seasons} season(s).',
                action,
                self.config['list'],
                **res[action],
            )
            for media_type, request in res['not_found'].items():
                if request:
                    logger.debug('not found {}: {}', media_type, request)
            # TODO: Improve messages about existing and unknown results
            # Mark the results expired if we added or removed anything
            if sum(res[action].values()):
                self.invalidate_cache()
        elif result.status_code == 404:
            logger.error('List does not appear to exist on trakt: {}', self.config['list'])
        elif result.status_code == 401:
            logger.error('Authentication error: have you authorized Flexget on Trakt.tv?')
            logger.debug('trakt response: {}', result.text)
        else:
            logger.error('Unknown error submitting data to trakt.tv: {}', result.text)
Beispiel #56
0
    def test_pending_list_entries(self, api_client, schema_match):
        # Get non existent list
        rsp = api_client.get('/pending_list/1/entries/')
        assert rsp.status_code == 404
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(base_message, data)
        assert not errors

        payload = {'name': 'test_list'}

        # Create list
        rsp = api_client.json_post('/pending_list/', data=json.dumps(payload))
        assert rsp.status_code == 201
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_list_base_object, data)
        assert not errors

        for field, value in payload.items():
            assert data.get(field) == value

        entry_data = {'title': 'title', 'original_url': 'http://test.com'}

        # Add entry to list
        rsp = api_client.json_post('/pending_list/1/entries/',
                                   data=json.dumps(entry_data))
        assert rsp.status_code == 201
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_list_entry_base_object, data)
        assert not errors

        for field, value in entry_data.items():
            assert data.get(field) == value

        # Get entries from list
        rsp = api_client.get('/pending_list/1/entries/')
        assert rsp.status_code == 200
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_lists_entries_return_object, data)
        assert not errors

        for field, value in entry_data.items():
            assert data[0].get(field) == value

        # Try to re-add entry to list
        rsp = api_client.json_post('/pending_list/1/entries/',
                                   data=json.dumps(entry_data))
        assert rsp.status_code == 409
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_list_entry_base_object, data)
        assert not errors

        # Try to post to non existing list
        rsp = api_client.json_post('/pending_list/10/entries/',
                                   data=json.dumps(entry_data))
        assert rsp.status_code == 404
        data = json.loads(rsp.get_data(as_text=True))

        errors = schema_match(OC.pending_list_entry_base_object, data)
        assert not errors
Beispiel #57
0
 def setter(self, entry):
     setattr(self, name, json.dumps(entry, encode_datetime=True))
Beispiel #58
0
    def on_task_output(self, task, config):
        """Submits accepted movies or episodes to trakt api."""
        if config.get('account') and not config.get('username'):
            config['username'] = '******'
        found = {'shows': [], 'movies': []}
        for entry in task.accepted:
            if 'series_name' in entry:
                show = {
                    'title': entry['series_name'],
                    'ids': get_entry_ids(entry)
                }
                if 'series_season' in entry:
                    season = {'number': entry['series_season']}
                    if 'series_episode' in entry:
                        season['episodes'] = [{
                            'number': entry['series_episode']
                        }]
                    show['seasons'] = [season]
                found['shows'].append(show)
            elif any(field in entry
                     for field in ['imdb_id', 'tmdb_id', 'movie_name']):
                movie = {'ids': get_entry_ids(entry)}
                if not movie['ids']:
                    movie['title'] = entry.get('movie_name') or entry.get(
                        'imdb_name')
                    movie['year'] = entry.get('movie_year') or entry.get(
                        'imdb_year')
                found['movies'].append(movie)

        if not (found['shows'] or found['movies']):
            self.log.debug('Nothing to submit to trakt.')
            return

        if config['list'] in ['collection', 'watchlist', 'watched']:
            args = ('sync', 'history'
                    if config['list'] == 'watched' else config['list'])
        else:
            args = ('users', config['username'], 'lists',
                    make_list_slug(config['list']), 'items')
        if self.remove:
            args += ('remove', )
        url = get_api_url(args)

        if task.manager.options.test:
            self.log.info('Not submitting to trakt.tv because of test mode.')
            return
        session = get_session(account=config.get('account'))
        self.log.debug('Submitting data to trakt.tv (%s): %s' % (url, found))
        try:
            result = session.post(url,
                                  data=json.dumps(found),
                                  raise_status=False)
        except RequestException as e:
            self.log.error('Error submitting data to trakt.tv: %s' % e)
            return
        if 200 <= result.status_code < 300:
            action = 'added'
            if self.remove:
                action = 'deleted'
            res = result.json()
            movies = res[action].get('movies', 0)
            eps = res[action].get('episodes', 0)
            self.log.info(
                'Successfully %s to/from list %s: %s movie(s), %s episode(s).'
                % (action, config['list'], movies, eps))
            for k, r in res['not_found'].iteritems():
                if r:
                    self.log.debug('not found %s: %s' % (k, r))
            # TODO: Improve messages about existing and unknown results
        elif result.status_code == 404:
            self.log.error('List does not appear to exist on trakt: %s' %
                           config['list'])
        elif result.status_code == 401:
            self.log.error(
                'Authentication error: have you authorized Flexget on Trakt.tv?'
            )
            self.log.debug('trakt response: ' + result.text)
        else:
            self.log.error('Unknown error submitting data to trakt.tv: %s' %
                           result.text)