Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
language: "python"
python: "3.6"
install:
- pip install requests pyyaml pytest-cov python-coveralls
- pip install requests pyyaml lxml pytest-cov python-coveralls httpretty codacy-coverage
script:
- python3 -m pytest --cov=.
after_success:
- coveralls
- coverage xml
- python-codacy-coverage -r coverage.xml
notifications:
- email: false
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ library, available at https://github.com/saik0/humblebundle-python
* Python 3.6
* requests library
* pyyaml library
* (optionnally for Humble Trove support) lxml

## Python Installation
Several features particular to Python v3.6 might have been used during the
Expand All @@ -32,12 +33,13 @@ https://www.python.org/downloads/ and grab the latest 3.x.x release.
## Getting the Prerequisites
From a command prompt, enter:

pip install requests
pip install pyyaml
pip install requests pyyaml lxml

You'll either be informed that the requirement is already satisfied, or pip
will retrieve, install, and configure the libraries for you.

Alternatively, you can run the `setup.py` script.

## Getting the Installation Files
Perform one of the following actions:
* Download the zip file from the [releases
Expand Down
55 changes: 7 additions & 48 deletions hb-downloader.py
Original file line number Diff line number Diff line change
@@ -1,53 +1,12 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import logger
from config_data import ConfigData
from configuration import Configuration
from event_handler import EventHandler
from humble_api.humble_api import HumbleApi
from actions import Action

__author__ = "Brian Schkerke"
__copyright__ = "Copyright 2016 Brian Schkerke"
__license__ = "MIT"


print("Humble Bundle Downloader v%s" % ConfigData.VERSION)
print("This program is not affiliated nor endorsed by Humble Bundle, Inc.")
print("For any suggestion or bug report, please create an issue at:\n%s" %
ConfigData.BUG_REPORT_URL)
print("")

# Load the configuration from the YAML file...
Configuration.load_configuration("hb-downloader-settings.yaml")
Configuration.parse_command_line()
Configuration.dump_configuration()
Configuration.push_configuration()

validation_status, message = Configuration.validate_configuration()
if not validation_status:
logger.display_message(False, "Error", message)
exit("Invalid configuration. Please check your command line arguments and"
"hb-downloader-settings.yaml.")

# Initialize the event handlers.
EventHandler.initialize()

hapi = HumbleApi(ConfigData.auth_sess_cookie)

if not hapi.check_login():
exit("Login to humblebundle.com failed."
" Please verify your authentication cookie")

logger.display_message(False, "Processing", "Downloading order list.")
game_keys = hapi.get_gamekeys()
logger.display_message(False, "Processing", "%s orders found." %
(len(game_keys)))

if ConfigData.action == "download":
Action.batch_download(hapi, game_keys)
else:
Action.list_downloads(hapi, game_keys)
import sys
import humble_downloader

__author__ = "Mayeul Cantan"
__copyright__ = "Copyright 2018 Mayeul Cantan"
__license__ = "MIT"

exit()
if __name__ == "__main__":
sys.exit(humble_downloader.main())
10 changes: 0 additions & 10 deletions humble_api/exceptions/__init__.py

This file was deleted.

62 changes: 62 additions & 0 deletions humble_downloader/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import humble_downloader.logger as logger
from humble_downloader.config_data import ConfigData
from .configuration import Configuration
from .event_handler import EventHandler
from .humble_api.humble_api import HumbleApi
from .actions import Action

__author__ = "Brian Schkerke"
__copyright__ = "Copyright 2016 Brian Schkerke"
__license__ = "MIT"

__all__ = ["ConfigData", "Action", "display_message", "HumbleDownload", "ProgressTracker"]


def main():
print("Humble Bundle Downloader v%s" % ConfigData.VERSION)
print("This program is not affiliated nor endorsed by Humble Bundle, Inc.")
print("For any suggestion or bug report, please create an issue at:\n%s" %
ConfigData.BUG_REPORT_URL)
print("")

# Load the configuration from the YAML file...
Configuration.load_configuration("hb-downloader-settings.yaml")
Configuration.parse_command_line()
Configuration.dump_configuration()
Configuration.push_configuration()

validation_status, message = Configuration.validate_configuration()
if not validation_status:
logger.display_message(False, "Error", message)
sys.exit("Invalid configuration. Please check your command line "
"arguments and hb-downloader-settings.yaml.")

# Initialize the event handlers.
EventHandler.initialize()

hapi = HumbleApi(ConfigData.auth_sess_cookie)

if not hapi.check_login():
exit("Login to humblebundle.com failed."
" Please verify your authentication cookie")

logger.display_message(False, "Processing", "Downloading order list.")
if ConfigData.restrict_keys:
game_keys = ConfigData.restrict_keys
else:
game_keys = hapi.get_gamekeys()
logger.display_message(False, "Processing", "%s orders found." %
(len(game_keys)))

if ConfigData.action == "download":
Action.batch_download(hapi, game_keys)
else:
Action.list_downloads(hapi, game_keys)


if __name__ == "__main__":
sys.exit(main())
15 changes: 10 additions & 5 deletions actions.py → humble_downloader/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@

__license__ = "MIT"

from humble_download import HumbleDownload
from progress_tracker import ProgressTracker
from config_data import ConfigData
import logger
from .humble_download import HumbleDownload
from .progress_tracker import ProgressTracker
from .config_data import ConfigData
from . import logger


class Action:
@staticmethod
def list_downloads(hapi, game_keys):
for key in game_keys:
if ConfigData.download_platforms.get("humble-keys", False):
print("%s" % key)
continue
selector_matched_key_once = False
current_order = hapi.get_order(key)
if 'subproducts' not in current_order:
continue

for current_subproduct in current_order.subproducts or []:
selector_matched_subproduct_once = False
Expand Down Expand Up @@ -66,7 +71,7 @@ def batch_download(hapi, game_keys):
key)
item_count_total += len(humble_downloads)
download_size_total += sum(
dl.humble_file_size for dl in humble_downloads)
dl.humble_file_size or 0 for dl in humble_downloads)
logger.display_message(False, "Processing",
"Added %d downloads for order %s"
% (len(humble_downloads), key))
Expand Down
File renamed without changes.
36 changes: 26 additions & 10 deletions configuration.py → humble_downloader/configuration.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import os
import yaml
import logger
from config_data import ConfigData
from humble_api.humble_hash import HumbleHash
from . import logger
from .config_data import ConfigData
from .humble_api.humble_hash import HumbleHash
from .humble_api.humble_api import HumbleApi

__author__ = "Brian Schkerke"
__copyright__ = "Copyright 2016 Brian Schkerke"
Expand Down Expand Up @@ -112,19 +114,27 @@ def parse_command_line():
"parameters are specified, this will default to "
"downloading everything in the library."))

a_list.add_argument(
"-u", "--print-url", action="store_true", dest="print_url",
help=("Print the download url with the output. Please note "
"that the url expires after a while"))

for action in [a_list, a_download]:
item_type = action.add_subparsers(title="type", dest="item_type")
games = item_type.add_parser("games")
games = item_type.add_parser("games", help="Only list games")
games.add_argument(
"--platform", nargs='+', choices=[ # TODO: add NATIVE?
"linux", "mac", "windows", "android", "asmjs"])
item_type.add_parser("ebooks")
item_type.add_parser("audio")
item_type.add_parser("ebooks", help="Only list ebooks")
item_type.add_parser("audio", help="Only display audio products")
if action is a_list:
item_type.add_parser("humble-keys", help=(
"Only list humble bundle keys that identify each "
"purchase"))
action.add_argument("-k", "--keys", nargs="+", help=(
"Only consider listed game key(s). Humble trove games are "
'considered a special "' + HumbleApi.TROVE_GAMEKEY + '" key'))

a_list.add_argument(
"-u", "--print-url", action="store_true", dest="print_url",
help=("Print the download url with the output. Please note "
"that the url expires after a while"))
args = parser.parse_args()

Configuration.configure_action(args)
Expand Down Expand Up @@ -154,9 +164,15 @@ def configure_action(args):
ConfigData.download_platforms[platform] = True
else:
ConfigData.download_platforms[platform] = False
# "fake platforms" can be defined to list info like humble keys
# TODO: only allow them for listing?
if args.item_type not in ConfigData.download_platforms:
ConfigData.download_platforms[args.item_type] = True
else:
args.action = "download"

ConfigData.action = args.action
ConfigData.restrict_keys = args.keys
ConfigData.print_url = args.print_url

@staticmethod
Expand Down
5 changes: 3 additions & 2 deletions event_handler.py → humble_downloader/event_handler.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import logger
from humble_api.events import Events
from . import logger
from .humble_api.events import Events

__author__ = "Brian Schkerke"
__copyright__ = "Copyright 2016 Brian Schkerke"
Expand Down
File renamed without changes.
File renamed without changes.
9 changes: 9 additions & 0 deletions humble_downloader/humble_api/exceptions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

__author__ = "Joel Pedraza"
__copyright__ = "Copyright 2014, Joel Pedraza"
__license__ = "MIT"

__all__ = ["HumbleAuthenticationException", "HumbleDownloadNeededException", "HumbleException", "HumbleParseException",
"HumbleResponseException"]
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import http.cookiejar
import itertools
from .model.order import Order
from .model.trove_order import TroveOrder
import requests
from .exceptions.humble_response_exception import HumbleResponseException
from .exceptions.humble_parse_exception import HumbleParseException
Expand All @@ -29,6 +30,10 @@ class HumbleApi(object):
LOGIN_URL = "https://www.humblebundle.com/processlogin"
ORDER_LIST_URL = "https://www.humblebundle.com/api/v1/user/order"
ORDER_URL = "https://www.humblebundle.com/api/v1/order/{order_id}"
TROVE_SIGN_URL= "https://www.humblebundle.com/api/v1/user/download/sign"
TROVE_PAGE_URL = "https://www.humblebundle.com/monthly/trove"

TROVE_GAMEKEY = TroveOrder.TROVE_GAMEKEY # Arbitrary gamekey used to identify humble trove orders

# default_headers specifies the default HTTP headers added to each request sent to the humblebundle.com servers.
default_headers = {
Expand Down Expand Up @@ -83,6 +88,22 @@ def check_login(self):
except HumbleAuthenticationException:
return False

def get_signed_trove_url(self, machine_name, filename):
"""
Transforms a machine name and a filename into valid URLs for the humble trove
parameters: machine name, filename

:param machine_name: A name given by humblebundle to the subproduct
:param filename: a name given by humblebundle to the downloaded file, as seen in the URL
:return: dict with signed_torrent_url and signed_url
"""
parameters = {"machine_name": machine_name, "filename": filename}
response = self._request("POST", HumbleApi.TROVE_SIGN_URL, data=parameters)
data = self.__parse_data(response)

if self.__authenticated_response_helper(response, data):
return data

def get_gamekeys(self, *args, **kwargs):
"""
Fetch all the gamekeys owned by an account.
Expand All @@ -103,7 +124,9 @@ def get_gamekeys(self, *args, **kwargs):
data = self.__parse_data(response)

if isinstance(data, list):
return [v["gamekey"] for v in data]
keys = [v["gamekey"] for v in data]
keys.append(HumbleApi.TROVE_GAMEKEY) # Unconditionnally include the humble trove key
return keys

# Let the helper function raise any common exceptions
self.__authenticated_response_helper(response, data)
Expand All @@ -125,6 +148,8 @@ def get_order(self, order_id, *args, **kwargs):
:raises HumbleResponseException: if the response was invalid
"""
url = HumbleApi.ORDER_URL.format(order_id=order_id)
if order_id == HumbleApi.TROVE_GAMEKEY:
return self.get_trove_items()

response = self._request("GET", url, *args, **kwargs)

Expand All @@ -139,6 +164,11 @@ def get_order(self, order_id, *args, **kwargs):
if self.__authenticated_response_helper(response, data):
return Order(data)

def get_trove_items(self):

trove_page = self._request("GET", HumbleApi.TROVE_PAGE_URL)
return TroveOrder(trove_page.text, self) # TODO error handling

def _request(self, *args, **kwargs):
"""
Set sane defaults that aren't session wide. Otherwise maintains the API of Session.request.
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading