Ejemplo n.º 1
0
 def _create_and_start_downloader(self,
                                  request_package_list: RequestPackageList,
                                  sync: bool) -> None:
     self._downloader = DownloadThread(
         self._parse_request_result,
         self._when_completing_a_round_of_requests,
         sync=sync)
     self._downloader.start(request_package_list)
Ejemplo n.º 2
0
class Task(BaseTask):
    """
    The default implement of default downloader.
    """
    def __init__(self, storage: StorageUnit):
        super().__init__(storage)
        self._increment = True
        self._timeout_increment = True
        self.set_result_convert_rules(self.rules_of_convert_params)

    def _create_and_start_downloader(self,
                                     request_package_list: RequestPackageList,
                                     sync: bool) -> None:
        self._downloader = DownloadThread(
            self._parse_request_result,
            self._when_completing_a_round_of_requests,
            sync=sync)
        self._downloader.start(request_package_list)

    def _convert_request_result(
            self,
            result: Future,
            round_end: bool = False) -> Union[ResultPackage, int]:
        if round_end is False:
            return result.result()
        else:
            return result.result().result

    def _restart(self) -> None:
        global CLIENT_SESSION_PARAMS
        if self._timeout_increment:
            now_timeout = CLIENT_SESSION_PARAMS.total_timeout
            CLIENT_SESSION_PARAMS.set_total_timeout(now_timeout + 30)
        return super()._restart()

    def rules_of_convert_params(self, result: Any) -> None:
        if type(result) is list:
            self._data.extend(result)
        else:
            self._data.append(result)

    # This function was not supposed to be here,
    # but to avoid adding too many intermediate layers,
    # it's better to put it here.
    # If an intermediate layer is used,
    # the hierarchy should look like this:
    # class Task:
    #     ...
    # class PixivTask(Task):
    #     def picture_parser(...):
    #         ...
    # class TokenTask(PixivTask):
    #     ...
    def picture_parser(self, result_package: ResultPackage) -> ParseResult:
        with open(path.join(self._abs_save_path, result_package.msg[0]),
                  'wb') as file_:
            file_.write(result_package.result)
        return ParseResult(result_package.msg[1], result_package.msg[0])
Ejemplo n.º 3
0
class TokenTask(Task):
    """
    Subclasses that inherit from the
    Task class in order to implement the token function
    """
    def __init__(self, storage: StorageUnit):
        super().__init__(storage)
        self._wait_token_fn = None
        self._wait_token_fn_args = ()
        self._wait_token_fn_kwargs = {}
        self._sync = False

        self._stopped = False
        self._lock = Lock()

    def get_token_callback(self, token: str) -> None:
        self._lock.acquire()
        if token == "Internet connection exception":
            self._stopped = True
            self._state = TaskState.Stopped

        if self._stopped:
            self._lock.release()
            return
        request_package_list = self._wait_token_fn(
            self, *(token, *self._wait_token_fn_args),
            **self._wait_token_fn_kwargs)
        self._downloader = DownloadThread(
            self._parse_request_result,
            self._when_completing_a_round_of_requests,
            sync=self._sync)
        self._downloader.start(request_package_list)
        self._state = TaskState.Downloading
        self._lock.release()
        # When you request pixiv api interface,
        # you must be use sync downloader.
        # Use async downloader will raise ConnectionResetError exception.
        # I don't know how to fixed it.

    def _start(self) -> None:
        self._lock.acquire()
        self._stopped = False
        self._lock.release()
        super()._start()

    def _stop(self) -> None:
        self._lock.acquire()
        self._stopped = True
        super()._stop()
        self._lock.release()

    def _safe_stop(self) -> None:
        self._lock.acquire()
        self._stopped = True
        super()._safe_stop()
        self._lock.release()
Ejemplo n.º 4
0
def download(photos, destfolder="photos", numthreads=4):
    queue = Queue()
    for photo in photos:
        queue.put(photo)

    for i in range(numthreads):
        t = DownloadThread(queue, destfolder)
        t.start()

    queue.join()
Ejemplo n.º 5
0
    def get_token_callback(self, token: str) -> None:
        self._lock.acquire()
        if token == "Internet connection exception":
            self._stopped = True
            self._state = TaskState.Stopped

        if self._stopped:
            self._lock.release()
            return
        request_package_list = self._wait_token_fn(
            self, *(token, *self._wait_token_fn_args),
            **self._wait_token_fn_kwargs)
        self._downloader = DownloadThread(
            self._parse_request_result,
            self._when_completing_a_round_of_requests,
            sync=self._sync)
        self._downloader.start(request_package_list)
        self._state = TaskState.Downloading
        self._lock.release()
Ejemplo n.º 6
0
    def refresh_auth_token(self,
                           username: str = None,
                           password: str = None,
                           refresh_token: str = None) -> bool:

        url = 'https://oauth.secure.pixiv.net/auth/token'
        now = datetime.now()
        x_client_time = now.strftime('%Y-%m-%dT%H:%M:%S+08:00')
        x_client_hash = md5(
            (x_client_time + self._LoginHash).encode()).hexdigest()
        headers = {
            'User-Agent': 'PixivAndroidApp/5.0.64 (Android 6.0)',
            'x-client-time': x_client_time,
            'x-client-hash': x_client_hash
        }
        data = {
            'get_secure_url': 1,
            'client_id': self._ClientID,
            'client_secret': self._ClientSecret,
        }

        if username is not None and password is not None:
            data['grant_type'] = 'password'
            data['username'] = username
            data['password'] = password
        elif refresh_token is not None or self._RefreshToken is not None:
            data['grant_type'] = 'refresh_token'
            data['refresh_token'] = refresh_token or self._RefreshToken
        else:
            raise Exception(
                'Refresh token, but not contain username and password or contain refresh token.'
            )

        request_package = RequestPackage(url, 'POST', headers, data=data)
        self._DownloadThread = DownloadThread(self._child_fetch_callback,
                                              lambda future: print(end=''),
                                              sync=True)
        self._DownloadThread.start([request_package])
        return True
Ejemplo n.º 7
0
class TokenManager:
    instance = None
    init = True

    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = super().__new__(cls, *args, **kwargs)
        return cls.instance

    def __init__(self, refresh_token: str = None):
        if not self.init:
            return
        self.init = False

        # build params
        self._ClientID = 'MOBrBDS8blbauoSck0ZfDbtuzpyT'
        self._ClientSecret = 'lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj'
        self._LoginHash = '28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c'

        # token
        self._AccessToken = None
        self._RefreshToken = refresh_token
        self._AccessTokenUpdateTime = 0

        # network
        self._DownloadThread = None

        # operating mechanism
        self._Refreshing = False
        self._SendTokenList = []

        self._load_setting()
        core_logger.info('Create TokenManager instance.')

    def _load_setting(self):
        # Pay attention to the real search path
        # Search path is running file path, not module path.
        if 'Token.json' in listdir('.'):
            with open('Token.json') as file:
                data = load(file)
                self._AccessToken = data['AccessToken']
                self._RefreshToken = data['RefreshToken']
                self._AccessTokenUpdateTime = data['AccessTokenUpdateTime']
            core_logger.info('Find token file, loading.')
            now_timestamp = int(datetime.now().timestamp())
            if now_timestamp - self._AccessTokenUpdateTime >= AccessTokenUpdateExpiration:
                self._AccessToken = None
                self._RefreshToken = None
                self._AccessTokenUpdateTime = 0
                core_logger.info('According to the last update date '
                                 'more than 30 days, the data is cleared')
        else:
            core_logger.info('Not find token file.')

    def set_refresh_token(self, refresh_token):
        self._RefreshToken = refresh_token
        core_logger.debug('Call function set_refresh_token.')

    @property
    def msg(self) -> str:
        if self._RefreshToken is None:
            return "You haven't logged in yet."
        update_time = datetime.fromtimestamp(
            self._AccessTokenUpdateTime).strftime('%Y-%m-%d %H:%M:%S')
        return f"AccessToken: {self._AccessToken}\n" \
               f"RefreshToken: {self._RefreshToken}\n" \
               f"AccessTokenUpdateTime: {update_time}"

    @property
    def has_refresh_token(self):
        if self._RefreshToken is None:
            return False
        else:
            return True

    def get_token(self, instance_callback: Callable[[str], None]) -> str:
        if int(datetime.now().timestamp()
               ) - self._AccessTokenUpdateTime < 45 * 60:
            return self._AccessToken
        else:
            if not self._Refreshing:
                self.refresh_auth_token()
                self._Refreshing = True
            self._SendTokenList.append(instance_callback)
            return ''

    def login(self, username: str, password: str) -> bool:
        core_logger.info('Login account.')
        return self.refresh_auth_token(username=username, password=password)

    def refresh_auth_token(self,
                           username: str = None,
                           password: str = None,
                           refresh_token: str = None) -> bool:

        url = 'https://oauth.secure.pixiv.net/auth/token'
        now = datetime.now()
        x_client_time = now.strftime('%Y-%m-%dT%H:%M:%S+08:00')
        x_client_hash = md5(
            (x_client_time + self._LoginHash).encode()).hexdigest()
        headers = {
            'User-Agent': 'PixivAndroidApp/5.0.64 (Android 6.0)',
            'x-client-time': x_client_time,
            'x-client-hash': x_client_hash
        }
        data = {
            'get_secure_url': 1,
            'client_id': self._ClientID,
            'client_secret': self._ClientSecret,
        }

        if username is not None and password is not None:
            data['grant_type'] = 'password'
            data['username'] = username
            data['password'] = password
        elif refresh_token is not None or self._RefreshToken is not None:
            data['grant_type'] = 'refresh_token'
            data['refresh_token'] = refresh_token or self._RefreshToken
        else:
            raise Exception(
                'Refresh token, but not contain username and password or contain refresh token.'
            )

        request_package = RequestPackage(url, 'POST', headers, data=data)
        self._DownloadThread = DownloadThread(self._child_fetch_callback,
                                              lambda future: print(end=''),
                                              sync=True)
        self._DownloadThread.start([request_package])
        return True
        # must be sync downloader
        # use async download will raise ConnectionResetError exception.
        # i don't know hwo to fixed it.

    def _child_fetch_callback(self, future: Future):
        result = future.result()  # type: ResultPackage
        if result.exception is None:
            return self._extract_token(result.result.decode())
        else:
            core_logger.warning(
                'Request refresh token failed, please check internet connection.'
            )
            self._Refreshing = False
            while self._SendTokenList:
                callback = self._SendTokenList.pop()
                callback("Internet connection exception")

    # FIXME: 未捕捉 son.decoder.JSONDecodeError 异常
    # 该异常出现于使用代理登陆或者刷新 token 时被 cloudflare 拦截
    def _extract_token(self, resp: str):
        try:
            resp = loads(resp)  # type: dict
            if resp.get('response', None) is not None:
                self._AccessToken = resp['response']['access_token']
                self._RefreshToken = resp['response']['refresh_token']
                self._AccessTokenUpdateTime = int(datetime.now().timestamp())
                # self._UserID = resp['user']['id']  # maybe will use it when multiple tokens
                core_logger.debug('Refresh token.')
            else:
                raise OperationFailedException(
                    'Operation failed, '
                    'please check username and password and refresh token.')
        except OperationFailedException:
            core_logger.warning(
                'Operation failed, please check username and password and refresh token.'
            )
        except KeyError:
            # 该异常未被捕捉,异常级别应为 CRITICAL,应组织程序有序的退出
            core_logger.critical('Extract access_token error!', exc_info=True)
            raise KeyError
        return self._write_token()

    def _write_token(self):
        with open('Token.json', 'w', encoding='utf-8') as file:
            dump(
                {
                    'AccessToken': self._AccessToken,
                    'RefreshToken': self._RefreshToken,
                    'AccessTokenUpdateTime': self._AccessTokenUpdateTime
                }, file)

        self._Refreshing = False
        self._send_token()

    def _send_token(self):
        """
        This function will definitely end
        because the callback functions are all functions that
        create a download thread and start it
        :return:
        """
        while self._SendTokenList:
            callback = self._SendTokenList.pop()
            callback(self._AccessToken)