about summary refs log tree commit diff
path: root/pkgs/games
diff options
context:
space:
mode:
authoraszlig <aszlig@nix.build>2019-09-30 02:50:25 +0200
committeraszlig <aszlig@nix.build>2019-09-30 03:43:03 +0200
commita6b50f6c417b568ed82f6370bbb65667b5ca48c7 (patch)
treecb083750ee0ff350ea215461793c7e36b7900697 /pkgs/games
parent050f4399a46a77bbbbe7194a42bbc7a6ee5a17ba (diff)
fetch-humble-bundle: Fix login with guard code
HumbleBundle has introduced a new guard code, which is sent to the email
address of the account owner after the captcha has been solved.

Unfortunately, the humblebundle-python[1] library is no longer
maintained and there are no forks with fixups. In addition, the code of
the library is sub-par and also doesn't support Python 3.

So I added a patch containing a _very_ dirty fix to include the guard
code during the login procedure for now until we have decided to either
go with the existing library and fork it or use another library or
implement it ourselves, since we only use a tiny fraction of the HB API.

I also fixed up the Qt plugin path for the captcha prompt if a newer
nixpkgs revision is used, where we have wrapQtAppsHook.

One downside of the guard code is that we get prompted for it _every_
time we download something, which might be annoying, but at least it's
better than not working at all.

Signed-off-by: aszlig <aszlig@nix.build>
Diffstat (limited to 'pkgs/games')
-rw-r--r--pkgs/games/humblebundle/fetch-humble-bundle/default.nix89
-rw-r--r--pkgs/games/humblebundle/fetch-humble-bundle/guard-code.patch121
2 files changed, 195 insertions, 15 deletions
diff --git a/pkgs/games/humblebundle/fetch-humble-bundle/default.nix b/pkgs/games/humblebundle/fetch-humble-bundle/default.nix
index f6f161dc..1340bce8 100644
--- a/pkgs/games/humblebundle/fetch-humble-bundle/default.nix
+++ b/pkgs/games/humblebundle/fetch-humble-bundle/default.nix
@@ -1,13 +1,20 @@
-{ stdenv, curl, cacert, writeText, fetchFromGitHub, fetchpatch
-, python, pythonPackages
+{ stdenv, curl, cacert, writeText, writeScript, fetchFromGitHub, fetchpatch
+, python, python3, pythonPackages
 
 # Dependencies for the captcha solver
-, pkgconfig, qt5, runCommandCC
+, pkgconfig, qt5
 
 , email, password
 }:
 
-{ name ? null, machineName, downloadName ? "Download", suffix ? "humblebundle", md5 }: let
+{ name ? null
+, machineName
+, downloadName ? "Download"
+, suffix ? "humblebundle"
+, md5
+}:
+
+let
   cafile = "${cacert}/etc/ssl/certs/ca-bundle.crt";
 
   getCaptcha = let
@@ -77,13 +84,34 @@
       }
     '';
 
-  in runCommandCC "get-captcha" {
-    nativeBuildInputs = [ pkgconfig ];
+  in stdenv.mkDerivation {
+    name = "get-captcha";
+
+    dontUnpack = true;
+
+    nativeBuildInputs = [ pkgconfig (qt5.wrapQtAppsHook or null) ];
     buildInputs = [ qt5.qtbase qt5.qtwebengine ];
     preferLocalBuild = true;
-  } ''
-    g++ $(pkg-config --libs --cflags Qt5WebEngineWidgets Qt5WebEngine) \
-      -Wall -std=c++11 -o "$out" ${application}
+
+    buildPhase = ''
+      g++ $(pkg-config --libs --cflags Qt5WebEngineWidgets Qt5WebEngine) \
+        -Wall -std=c++11 -o get-captcha ${application}
+    '';
+
+    installPhase = ''
+      install -vD get-captcha "$out/bin/get-captcha"
+    '';
+  };
+
+  getGuard = writeScript "get-guard" ''
+    #!${python3.interpreter}
+    import socket
+    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
+      sock.bind(('localhost', 18129))
+      sock.listen(1)
+      with sock.accept()[0] as conn:
+        guard = input("Guard code: ")
+        conn.sendall(guard.encode())
   '';
 
   humbleAPI = pythonPackages.buildPythonPackage rec {
@@ -97,6 +125,8 @@
       sha256 = "1kcg42nh7sbjabim1pbqx14468pypznjy7fx2bv7dicy0sqd9b8j";
     };
 
+    patches = [ ./guard-code.patch ];
+
     postPatch = ''
       sed -i -e '/^LOGIN_URL *=/s,/login,/processlogin,' humblebundle/client.py
       sed -i -e '/self\.supports_canonical.*data.*supports_canonical/d' \
@@ -138,7 +168,8 @@
 
     def login_with_captcha(hb):
       print >>sys.stderr, "Solving a captcha is required to log in."
-      print >>sys.stderr, "Please run " ${pyStr (toString getCaptcha)} " now."
+      print >>sys.stderr, "Please run " ${pyStr (toString getCaptcha)} \
+                          "/bin/get-captcha now."
       sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
       print >>sys.stderr, "Waiting for connection",
       i = 0
@@ -153,13 +184,41 @@
       response = sock.recv(4096)
       sock.close()
       print >>sys.stderr, "Captcha solved correctly, logging in."
-      hb.login(${pyStr email}, ${pyStr password}, recaptcha_response=response)
+      api_login(hb, recaptcha_response=response)
+
+    def login_with_guard(hb, skip_code):
+      print >>sys.stderr, "A guard code has been sent to your email address."
+      print >>sys.stderr, "Please run " ${pyStr (toString getGuard)} " now."
+      sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+      print >>sys.stderr, "Waiting for connection",
+      # XXX: DRY!
+      i = 0
+      while sock.connect_ex(("127.0.0.1", 18129)) != 0:
+        time.sleep(0.1)
+        if i % 10 == 0:
+          sys.stderr.write('.')
+          sys.stderr.flush()
+        i += 1
+      print >>sys.stderr, " connected."
+      print >>sys.stderr, "Waiting for guard code..."
+      response = sock.recv(4096)
+      sock.close()
+      print >>sys.stderr, "Guard code supplied, logging in."
+      api_login(hb, captcha_skip_code=skip_code, guard_code=response)
+
+    def api_login(hb, recaptcha_response=None,
+                  captcha_skip_code=None, guard_code=None):
+      try:
+        hb.login(${pyStr email}, ${pyStr password},
+                 recaptcha_response=recaptcha_response,
+                 captcha_skip_code=captcha_skip_code, guard_code=guard_code)
+      except humblebundle.exceptions.HumbleCaptchaException:
+        login_with_captcha(hb)
+      except humblebundle.exceptions.HumbleGuardRequiredException as e:
+        login_with_guard(hb, e.captcha_skip_code)
 
     hb = humblebundle.HumbleApi()
-    try:
-      hb.login(${pyStr email}, ${pyStr password})
-    except humblebundle.exceptions.HumbleCaptchaException:
-      login_with_captcha(hb)
+    api_login(hb)
 
     products = dict(get_products(hb))
     dstruct = find_download(products)
diff --git a/pkgs/games/humblebundle/fetch-humble-bundle/guard-code.patch b/pkgs/games/humblebundle/fetch-humble-bundle/guard-code.patch
new file mode 100644
index 00000000..f17928ae
--- /dev/null
+++ b/pkgs/games/humblebundle/fetch-humble-bundle/guard-code.patch
@@ -0,0 +1,121 @@
+diff --git a/humblebundle/client.py b/humblebundle/client.py
+index fbc31c9..44184a1 100644
+--- a/humblebundle/client.py
++++ b/humblebundle/client.py
+@@ -75,7 +75,9 @@ class HumbleApi(object):
+     """
+ 
+     @callback
+-    def login(self, username, password, authy_token=None, recaptcha_challenge=None, recaptcha_response=None,
++    def login(self, username, password, authy_token=None,
++              recaptcha_challenge=None, recaptcha_response=None,
++              guard_code=None, captcha_skip_code=None,
+               *args, **kwargs):
+         """
+         Login to the Humble Bundle API. The response sets the _simpleauth_sess cookie which is stored in the session
+@@ -87,6 +89,8 @@ class HumbleApi(object):
+         :type authy_token: integer or str
+         :param str recaptcha_challenge: (optional) The challenge signed by Humble Bundle's public key from reCAPTCHA
+         :param str recaptcha_response: (optional) The plaintext solved CAPTCHA
++        :param str guard_code: (optional) The guard code sent via email
++        :param str captcha_skip_code: (optional) A token to skip the CAPTCHA
+         :param list args: (optional) Extra positional args to pass to the request
+         :param dict kwargs: (optional) Extra keyword args to pass to the request. If a data dict is supplied a key
+                             collision with any of the above params will resolved in favor of the supplied param
+@@ -108,7 +112,9 @@ class HumbleApi(object):
+             'password': password,
+             'authy-token': authy_token,
+             'recaptcha_challenge_field': recaptcha_challenge,
+-            'recaptcha_response_field': recaptcha_response}
++            'recaptcha_response_field': recaptcha_response,
++            'guard': guard_code,
++            'captcha-skip-code': captcha_skip_code}
+         kwargs.setdefault('data', {}).update({k: v for k, v in default_data.items() if v is not None})
+ 
+         response = self._request('POST', LOGIN_URL, *args, **kwargs)
+diff --git a/humblebundle/exceptions.py b/humblebundle/exceptions.py
+index 9041219..fe4eeaf 100644
+--- a/humblebundle/exceptions.py
++++ b/humblebundle/exceptions.py
+@@ -9,7 +9,7 @@ __copyright__ = "Copyright 2014, Joel Pedraza"
+ __license__ = "MIT"
+ 
+ __all__ = ['HumbleException', 'HumbleResponseException', 'HumbleAuthenticationException', 'HumbleCredentialException',
+-           'HumbleCaptchaException', 'HumbleTwoFactorException', 'HumbleParseException']
++           'HumbleCaptchaException', 'HumbleTwoFactorException', 'HumbleGuardRequiredException', 'HumbleParseException']
+ 
+ from requests import RequestException
+ 
+@@ -38,6 +38,7 @@ class HumbleAuthenticationException(HumbleResponseException):
+     def __init__(self, *args, **kwargs):
+         self.captcha_required = kwargs.pop('captcha_required', None)
+         self.authy_required = kwargs.pop('authy_required', None)
++        self.captcha_skip_code = kwargs.pop('captcha_skip_code', None)
+         super(HumbleAuthenticationException, self).__init__(*args, **kwargs)
+ 
+ 
+@@ -62,6 +63,13 @@ class HumbleTwoFactorException(HumbleAuthenticationException):
+     pass
+ 
+ 
++class HumbleGuardRequiredException(HumbleAuthenticationException):
++    """
++    A guard code is required
++    """
++    pass
++
++
+ class HumbleParseException(HumbleResponseException):
+     """
+     An error occurred while parsing
+diff --git a/humblebundle/handlers.py b/humblebundle/handlers.py
+index 36fc6e1..a8acebf 100644
+--- a/humblebundle/handlers.py
++++ b/humblebundle/handlers.py
+@@ -64,29 +64,42 @@ def login_handler(client, response):
+     success = data.get('success', None)
+     if success is True:
+         return True
++    if data.get('goto', None) is not None:
++        return True
+ 
+     captcha_required = data.get('captcha_required')
+     authy_required = data.get('authy_required')
++    captcha_skip_code = data.get('skip_code', [None])[0]
++
++    guard = data.get('humble_guard_required', False)
++    if guard:
++        raise HumbleGuardRequiredException('Guard code required', request=response.request, response=response,
++                                           captcha_required=captcha_required, authy_required=authy_required,
++                                           captcha_skip_code=captcha_skip_code)
+ 
+     errors, error_msg = get_errors(data)
+     if errors:
+         captcha = errors.get('captcha')
+         if captcha:
+             raise HumbleCaptchaException(error_msg, request=response.request, response=response,
+-                                         captcha_required=captcha_required, authy_required=authy_required)
++                                         captcha_required=captcha_required, authy_required=authy_required,
++                                         captcha_skip_code=captcha_skip_code)
+ 
+         username = errors.get('username')
+         if username:
+             raise HumbleCredentialException(error_msg, request=response.request, response=response,
+-                                            captcha_required=captcha_required, authy_required=authy_required)
++                                            captcha_required=captcha_required, authy_required=authy_required,
++                                            captcha_skip_code=captcha_skip_code)
+ 
+         authy_token = errors.get("authy-token")
+         if authy_token:
+             raise HumbleTwoFactorException(error_msg, request=response.request, response=response,
+-                                           captcha_required=captcha_required, authy_required=authy_required)
++                                           captcha_required=captcha_required, authy_required=authy_required,
++                                           captcha_skip_code=captcha_skip_code)
+ 
+     raise HumbleAuthenticationException(error_msg, request=response.request, response=response,
+-                                        captcha_required=captcha_required, authy_required=authy_required)
++                                        captcha_required=captcha_required, authy_required=authy_required,
++                                        captcha_skip_code=captcha_skip_code)
+ 
+ 
+ def gamekeys_handler(client, response):