class PixivAPIs: def __init__(self, credentials): self.apapi = AppPixivAPI() self.papi = PixivAPI() self.apapi.auth(refresh_token=credentials[0]) self.papi.auth(refresh_token=credentials[0])
class PixivHandler: def __init__(self, name, app_config={}): config_path = Path(app_config.get('handlers_config_dir', '.')) / 'pixiv.toml' data_path = Path(app_config.get('data_dir', './data/')) / '{}.toml'.format(name) self.config = Config(config_path, write_defaults=True, defaults={ 'refresh': 'xxxx', }) self.config.save() self.data = Config(data_path) self.age_filter = None self.api = PixivAPI() if self.config.get('refresh'): print('logging in to Pixiv...') login_response = self.api.auth(refresh_token=self.config['refresh']) print('logged in into account {0.name} ({0.account}) [{0.id}]'.format(login_response['response']['user'])) def set_age_filter(self, filter): self.age_filter = filter def handle(self, feed): if feed == 'followings': data = self.api.me_following_works(image_sizes=['large', 'medium'], include_stats=False) elif feed == 'bookmarks': data = self.api.me_favorite_works() else: return [] if data['status'] != 'success': print('invalid response') print('got:') print(data) return [] results = data['response'] save_data = self.data.get(feed, {'last_id': 0}) print('latest id: {}'.format(save_data.get('last_id'))) results = list(filter(lambda x: x['id'] > save_data.get('last_id'), results)) if len(results) == 0: return [] save_data['last_id'] = results[0]['id'] self.data[feed] = save_data self.data.save() ret = [] for entry in results: print('Handling pixiv entry {}'.format(entry['id'])) if self.age_filter != None: if entry['age_limit'] in ['r18', 'r18-g'] and self.age_filter == 'safe': print('skipping because currently in safe mode') continue if entry['age_limit'] == 'all-age' and self.age_filter == 'r18': print('skipping because currently in r18 mode') continue content = '<https://www.pixiv.net/artworks/{}>'.format(entry['id']) content += '\n{} by {} ({})'.format(entry['title'], entry['user']['name'], entry['user']['account']) content += '\nTags: {}'.format(' '.join(entry['tags'])) if entry['is_manga']: print('it\'s a manga') work = self.api.works(entry['id']) if work['status'] != 'success': continue work = work['response'] if len(work) == 0: continue work = work[0] urls = [x['image_urls']['medium'] for x in work['metadata']['pages']] if len(urls) > 4: content += '\n{} more pictures not shown here'.format(len(urls) - 4) urls = urls[:4] else: if entry['width'] > 2000 or entry['height'] > 2000: content += '\n(not displaying full resolution because it is too large)' urls = [entry['image_urls']['medium']] else: urls = [entry['image_urls']['large']] files = [] index = 0 for url in urls: print('downloading picture...') response = requests.get(url, headers={'referer': 'https://pixiv.net'}) if response.status_code != 200: continue ext = Path(url).suffix files.append({'data': response.content, 'name': 'page{}{}'.format(index, ext)}) index += 1 ret.append({'content': content, 'files': files}) ret.reverse() return ret
class CustomPixivPy: """ A wrapper around PixivAPI and AppPixivAPI to facilitate automatic re-authentication (for required methods) and custom result format """ TOKEN_LIFESPAN = datetime.timedelta(seconds=3600) MAX_PIXIV_RESULTS = 3000 RESULTS_PER_QUERY = 50 MAX_RETRIES = 5 def __init__(self, **kwargs): super().__init__(**kwargs) # forces reauth() to trigger if any method is called: self.last_auth = datetime.datetime.fromtimestamp(0) self.refresh_token = "" self.aapi = AppPixivAPI(**kwargs) self.papi = PixivAPI(**kwargs) def login(self, refresh_token): self.refresh_token = refresh_token self.aapi.auth(refresh_token=refresh_token) self.papi.auth(refresh_token=refresh_token) self.last_auth = datetime.datetime.now() logger.debug('Pyxiv login done') return self # allows chaining @retry def illust_ranking(self, mode='daily', offset=None): self.reauth() offset = (offset or 0) // self.RESULTS_PER_QUERY + 1 return self.papi.ranking('illust', mode, offset, include_stats=False, image_sizes=['medium', 'large']) @retry def search_illust(self, word, search_target='text', sort='date', offset=None): self.reauth() offset = (offset or 0) // self.RESULTS_PER_QUERY + 1 return self.papi.search_works(word, offset, mode=search_target, types=['illustration'], sort=sort, include_stats=False, image_sizes=['medium', 'large']) @retry def illust_detail(self, illust_id, req_auth=True): self.reauth() return self.aapi.illust_detail(illust_id, req_auth) def reauth(self): """Re-authenticates with pixiv if the last login was more than TOKEN_LIFESPAN ago""" if datetime.datetime.now() - self.last_auth > self.TOKEN_LIFESPAN: self.login(self.refresh_token) self.papi.auth(refresh_token=self.refresh_token) logger.debug("Reauth successful") self.last_auth = datetime.datetime.now() def get_pixiv_results(self, offset=None, *, query="", nsfw=False): """ Get results from Pixiv as a dict If no parameters are given, SFW daily ranking is returned :param offset: Optional. page offset :param query: Optional. Specify a search query :param nsfw: Whether to allow NSFW illustrations, false by default :return: list of dicts containing illustration information """ json_result, last_error = None, None for attempt in range(1, self.MAX_RETRIES + 1): try: json_result = self.search_illust(query, offset=offset, sort='popular') \ if query else self.illust_ranking('daily_r18' if nsfw else 'daily', offset=offset) except PixivError as e: if attempt == self.MAX_RETRIES: logger.warning("Failed fetching Pixiv data: %s", e) raise e from None else: break results = [] if json_result.get('has_error'): return results it = json_result.response if query else ( x['work'] for x in json_result.response[0]['works']) for img in it: if not nsfw and img['sanity_level'] == 'black': continue # white = SFW, semi_black = questionable, black = NSFW results.append({ 'url': img['image_urls']['large'], 'thumb_url': img['image_urls']['medium'], 'title': img['title'], 'user_name': img['user']['name'], 'user_link': f"https://www.pixiv.net/en/users/{img['user']['id']}" }) logger.debug(results[-1]) return results