text: from loguru import logger
  
  from flexget import plugin
  from flexget.components.sites.utils import normalize_scene
  from flexget.config_schema import one_or_more
  from flexget.entry import Entry
  from flexget.event import event
  from flexget.plugin import PluginError
  from flexget.utils.requests import RequestException, Session, TimedLimiter
  
  logger = logger.bind(name='rarbg')
  
  requests = Session()
  requests.add_domain_limiter(
      TimedLimiter('torrentapi.org', '3 seconds')
  )  # they only allow 1 request per 2 seconds
  
  CATEGORIES = {
      'all': 0,
      # Movies
      'x264': 17,
      'x264 720p': 45,
      'x264 1080p': 44,
      'x264 3D': 47,
      'XviD': 14,
      'XviD 720p': 48,
      'Full BD': 42,
      # TV
      'HDTV': 41,
      'SDTV': 18,
      # Adult
      'XXX': 4,
      # Music
      'MusicMP3': 23,
      'MusicFLAC': 25,
      # Games
      'Games/PC ISO': 27,
      'Games/PC RIP': 28,
      'Games/PS3': 40,
      'Games/XBOX-360': 32,
      'Software/PC ISO': 33,
      # E-Books
      'e-Books': 35,
  }
  
  
  class SearchRarBG:
      """
          RarBG search plugin. Implements https://torrentapi.org/apidocs_v2.txt
  
          To perform search against single category:
  
          rarbg:
              category: x264 720p
  
          To perform search against multiple categories:
  
          rarbg:
              category:
                  - x264 720p
                  - x264 1080p
  
          Movie categories accepted: x264 720p, x264 1080p, XviD, Full BD
          TV categories accepted: HDTV, SDTV
  
          You can use also use category ID manually if you so desire (eg. x264 720p is actually category id '45')
      """
  
      schema = {
          'type': 'object',
          'properties': {
              'category': one_or_more(
                  {'oneOf': [{'type': 'integer'}, {'type': 'string', 'enum': list(CATEGORIES)}]}
              ),
              'sorted_by': {
                  'type': 'string',
                  'enum': ['seeders', 'leechers', 'last'],
                  'default': 'last',
              },
              # min_seeders and min_leechers seem to be working again
              'min_seeders': {'type': 'integer', 'default': 0},
              'min_leechers': {'type': 'integer', 'default': 0},
              'limit': {'type': 'integer', 'enum': [25, 50, 100], 'default': 25},
              'ranked': {'type': 'boolean', 'default': True},
              'use_tvdb': {'type': 'boolean', 'default': False},
          },
          "additionalProperties": False,
      }
  
      base_url = 'https://torrentapi.org/pubapi_v2.php'
      token = None
  
      def get_token(self, refresh=False):
          if refresh or not self.token:
              try:
                  response = requests.get(
                      self.base_url,
                      params={'get_token': 'get_token', 'format': 'json', 'app_id': 'flexget'},
                  ).json()
                  self.token = response.get('token')
                  logger.debug('RarBG token: %s', self.token)
                  requests.domain_limiters['torrentapi.org']()
              except RequestException as e:
                  logger.opt(exception=True).debug('Could not retrieve RarBG token')
                  raise PluginError('Could not retrieve token: %s' % e)
          return self.token
  
      def get(self, params, token_error=False):
          """
          Simple get-wrapper that allows updating invalid tokens
  
          :param params: the params to be passed to requests
          :param token_error: whether or not we previously have had token errors, if True we should fetch a new one
          :return: json response
          """
          params['token'] = self.get_token(refresh=token_error)
          try:
              response = requests.get(self.base_url, params=params)
              logger.debug('requesting: %s', response.url)
              response = response.json()
          except RequestException as e:
              logger.error('Rarbg request failed, will try again: %s', e)
              try:
                  response = requests.get(self.base_url, params=params)
              except RequestException as e:
                  logger.error('Rarbg request failed, giving up: %s', e)
              return
  
          # error code 1, 2 and 4 pertain to token errors
          if response.get('error_code') in [1, 2, 4]:
              logger.debug(
                  'Invalid token. Error {}: {}', response['error_code'], response.get('error')
              )
              if token_error:
                  raise PluginError('Could not retrieve a valid token: %s' % response.get('error'))
              return self.get(params=params, token_error=True)
  
          return response
  
      @plugin.internet(logger)
      def search(self, task, entry, config):
          """
              Search for entries on RarBG
          """
  
          categories = config.get('category', 'all')
          # Ensure categories a list
          if not isinstance(categories, list):
              categories = [categories]
          # Convert named category to its respective category id number
          categories = [c if isinstance(c, int) else CATEGORIES[c] for c in categories]
          category_url_fragment = ';'.join(str(c) for c in categories)
  
          entries = set()
  
          params = {
              'mode': 'search',
              'ranked': int(config['ranked']),
              'min_seeders': config['min_seeders'],
              'min_leechers': config['min_leechers'],
              'sort': config['sorted_by'],
              'category': category_url_fragment,
              'format': 'json_extended',
              'app_id': 'flexget',
          }
  
          for search_string in entry.get('search_strings', [entry['title']]):
              params.pop('search_string', None)
              params.pop('search_imdb', None)
              params.pop('search_tvdb', None)
  
              if entry.get('movie_name') and entry.get('imdb_id'):
                  params['search_imdb'] = entry.get('imdb_id')
              else:
                  query = normalize_scene(search_string)
                  query_url_fragment = query.encode('utf8')
                  params['search_string'] = query_url_fragment
                  if config['use_tvdb']:
                      plugin.get('thetvdb_lookup', self).lazy_series_lookup(entry, 'en')
                      params['search_tvdb'] = entry.get('tvdb_id')
                      logger.debug('Using tvdb id {}', entry.get('tvdb_id'))
  
              response = self.get(params=params)
              if not response:
                  continue
  
              # error code 10 and 20 just mean no results were found
              if response.get('error_code') in [10, 20]:
                  searched_string = (
                      params.get('search_string')
                      or 'imdb={0}'.format(params.get('search_imdb'))
                      or 'tvdb={0}'.format(params.get('tvdb_id'))
                  )
                  logger.debug(
                      'No results found for {}. Message from rarbg: {}',
                      searched_string,
                      response.get('error'),
                  )
                  continue
              elif response.get('error'):
                  logger.error(
                      'Error code {}: {}', response.get('error_code'), response.get('error')
                  )
                  continue
              else:
                  for result in response.get('torrent_results'):
                      e = Entry()
  
                      e['title'] = result.get('title')
                      e['url'] = result.get('download')
                      e['torrent_seeds'] = int(result.get('seeders'))
                      e['torrent_leeches'] = int(result.get('leechers'))
                      e['content_size'] = int(result.get('size')) / 1024 / 1024
                      episode_info = result.get('episode_info')
                      if episode_info:
                          e['imdb_id'] = episode_info.get('imdb')
                          e['tvdb_id'] = episode_info.get('tvdb')
                          e['tvrage_id'] = episode_info.get('tvrage')
  
                      entries.add(e)
  
          return entries
  
  
  @event('plugin.register')
  def register_plugin():
      plugin.register(SearchRarBG, 'rarbg', interfaces=['search'], api_ver=2)
  