about summary refs log tree commit diff
path: root/pkgs/games/gog/fetch-gog
diff options
context:
space:
mode:
authoraszlig <aszlig@nix.build>2018-02-22 05:09:09 +0100
committeraszlig <aszlig@nix.build>2018-02-22 05:09:09 +0100
commit5136f67286519753b9696b4ac46a0e7afd0efe43 (patch)
tree9e1826b5907aa8b16669bf8474ea25dac9a905cf /pkgs/games/gog/fetch-gog
parent8c52f23b831949deb708de4c463fd1f6251e6728 (diff)
fetch-gog: Try to login without captcha
On some occasions a captcha isn't needed for login (not sure exactly
when this is the case), so we really should just log in without it
instead of bailing out with an error.

Signed-off-by: aszlig <aszlig@nix.build>
Diffstat (limited to 'pkgs/games/gog/fetch-gog')
-rw-r--r--pkgs/games/gog/fetch-gog/default.nix85
-rw-r--r--pkgs/games/gog/fetch-gog/hexify-char.nix258
2 files changed, 327 insertions, 16 deletions
diff --git a/pkgs/games/gog/fetch-gog/default.nix b/pkgs/games/gog/fetch-gog/default.nix
index 3389eb60..e16f9e87 100644
--- a/pkgs/games/gog/fetch-gog/default.nix
+++ b/pkgs/games/gog/fetch-gog/default.nix
@@ -9,6 +9,33 @@
 }:
 
 let
+  # Taken from lgogdownloader (https://github.com/Sude-/lgogdownloader):
+  clientId = "46899977096215655";
+  clientSecret = "9d85c43b1482497dbbce61f6e4aa173a"
+               + "433796eeae2ca8c5f6129f2dc4de46d9";
+  redirectUri = "https://embed.gog.com/on_login_success?origin=client";
+
+  urlencode = url: query: let
+    urlquote = isQstring: val: let
+      extraSafeChars = lib.optionalString (!isQstring) "/:";
+      safeChars = lib.lowerChars ++ lib.upperChars
+               ++ lib.stringToCharacters ("0123456789_.-" + extraSafeChars);
+      charList = lib.stringToCharacters val;
+      hexify = chr: "%${import ./hexify-char.nix chr}";
+      quoteChar = chr: if lib.elem chr safeChars then chr else hexify chr;
+    in lib.concatMapStrings quoteChar charList;
+    mkKeyVal = key: val: "${urlquote true key}=${urlquote true val}";
+    qstring = lib.concatStringsSep "&" (lib.mapAttrsToList mkKeyVal query);
+  in urlquote false url + lib.optionalString (query != {}) "?${qstring}";
+
+  authURL = urlencode "https://auth.gog.com/auth" {
+    client_id = clientId;
+    redirect_uri = redirectUri;
+    response_type = "code";
+    layout = "default";
+    brand = "gog";
+  };
+
   getCaptcha = let
     mkCString = val: let
       escaped = lib.replaceStrings ["\"" "\\" "\n"] ["\\\"" "\\\\" "\\n"] val;
@@ -44,12 +71,9 @@ let
       #include <QQuickWebEngineProfile>
       #include <QUrlQuery>
 
-      // Taken from lgogdownloader (https://github.com/Sude-/lgogdownloader):
-      static QString clientId = "46899977096215655";
-      static QString clientSecret =
-        "9d85c43b1482497dbbce61f6e4aa173a433796eeae2ca8c5f6129f2dc4de46d9";
-      static QString redirectUri =
-        "https://embed.gog.com/on_login_success?origin=client";
+      static QString clientId = ${mkCString clientId};
+      static QString clientSecret = ${mkCString clientSecret};
+      static QString redirectUri = ${mkCString redirectUri};
 
       static QUrl getAuthUrl() {
         QUrl url("https://auth.gog.com/auth");
@@ -136,13 +160,48 @@ let
   fetcher = writeText "fetch-gog.py" ''
     import sys, socket, time
     from urllib.request import urlopen, Request
+    from urllib.parse import urlsplit, urlencode, parse_qs
     from urllib.error import HTTPError
     from json import loads
 
+    import mechanicalsoup
     from tabulate import tabulate
 
     class GogFetcher:
       def __init__(self, product_id, download_type, download_name):
+        self.product_id = product_id
+        self.download_type = download_type
+        self.download_name = download_name
+        self.login()
+
+      def login(self):
+        browser = mechanicalsoup.StatefulBrowser()
+        response = browser.open(${mkPyStr authURL})
+        if "google.com/recaptcha" in response.text:
+          token_url = self.login_with_captcha()
+        else:
+          browser.select_form('form[name="login"]')
+          browser['login[username]'] = ${mkPyStr email}
+          browser['login[password]'] = ${mkPyStr password}
+          browser.submit_selected()
+
+          auth_code = parse_qs(urlsplit(browser.get_url()).query)['code']
+
+          token_url = "https://auth.gog.com/token?" + urlencode({
+            'client_id': ${mkPyStr clientId},
+            'client_secret': ${mkPyStr clientSecret},
+            'grant_type': 'authorization_code',
+            'code': auth_code,
+            'redirect_uri': ${mkPyStr redirectUri}
+          })
+
+        response = urlopen(
+          token_url, cafile=${mkPyStr "${cacert}/etc/ssl/certs/ca-bundle.crt"}
+        )
+
+        self.access_token = loads(response.read())['access_token']
+
+      def login_with_captcha(self):
         sys.stderr.write("Solving a captcha is required to log in.\n")
         sys.stderr.write("Please run " ${mkPyStr getCaptcha} " now.\n")
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -159,15 +218,7 @@ let
         token_url = sock.recv(4096)
         sock.close()
         sys.stderr.write("Captcha solved correctly, logging in.\n")
-        response = urlopen(
-          token_url.decode(),
-          cafile=${mkPyStr "${cacert}/etc/ssl/certs/ca-bundle.crt"}
-        )
-
-        self.product_id = product_id
-        self.download_type = download_type
-        self.download_name = download_name
-        self.access_token = loads(response.read())['access_token']
+        return token_url.decode()
 
       def request(self, url):
         headers = {"Authorization": "Bearer " + self.access_token}
@@ -227,7 +278,9 @@ in stdenv.mkDerivation {
   outputHashAlgo = "sha256";
   outputHash = sha256;
 
-  nativeBuildInputs = [ curl python3Packages.tabulate ];
+  nativeBuildInputs = [
+    curl python3Packages.tabulate python3Packages.MechanicalSoup
+  ];
 
   buildCommand = ''
     url="$(${python3Packages.python.interpreter} ${fetcher} \
diff --git a/pkgs/games/gog/fetch-gog/hexify-char.nix b/pkgs/games/gog/fetch-gog/hexify-char.nix
new file mode 100644
index 00000000..78ae9159
--- /dev/null
+++ b/pkgs/games/gog/fetch-gog/hexify-char.nix
@@ -0,0 +1,258 @@
+val:
+
+if val == "" then "01"
+else if val == "" then "02"
+else if val == "" then "03"
+else if val == "" then "04"
+else if val == "" then "05"
+else if val == "" then "06"
+else if val == "" then "07"
+else if val == "" then "08"
+else if val == "\t" then "09"
+else if val == "\n" then "0a"
+else if val == "" then "0b"
+else if val == "" then "0c"
+else if val == "\r" then "0d"
+else if val == "" then "0e"
+else if val == "" then "0f"
+else if val == "" then "10"
+else if val == "" then "11"
+else if val == "" then "12"
+else if val == "" then "13"
+else if val == "" then "14"
+else if val == "" then "15"
+else if val == "" then "16"
+else if val == "" then "17"
+else if val == "" then "18"
+else if val == "" then "19"
+else if val == "" then "1a"
+else if val == "" then "1b"
+else if val == "" then "1c"
+else if val == "" then "1d"
+else if val == "" then "1e"
+else if val == "" then "1f"
+else if val == " " then "20"
+else if val == "!" then "21"
+else if val == "\"" then "22"
+else if val == "#" then "23"
+else if val == "$" then "24"
+else if val == "%" then "25"
+else if val == "&" then "26"
+else if val == "'" then "27"
+else if val == "(" then "28"
+else if val == ")" then "29"
+else if val == "*" then "2a"
+else if val == "+" then "2b"
+else if val == "," then "2c"
+else if val == "-" then "2d"
+else if val == "." then "2e"
+else if val == "/" then "2f"
+else if val == "0" then "30"
+else if val == "1" then "31"
+else if val == "2" then "32"
+else if val == "3" then "33"
+else if val == "4" then "34"
+else if val == "5" then "35"
+else if val == "6" then "36"
+else if val == "7" then "37"
+else if val == "8" then "38"
+else if val == "9" then "39"
+else if val == ":" then "3a"
+else if val == ";" then "3b"
+else if val == "<" then "3c"
+else if val == "=" then "3d"
+else if val == ">" then "3e"
+else if val == "?" then "3f"
+else if val == "@" then "40"
+else if val == "A" then "41"
+else if val == "B" then "42"
+else if val == "C" then "43"
+else if val == "D" then "44"
+else if val == "E" then "45"
+else if val == "F" then "46"
+else if val == "G" then "47"
+else if val == "H" then "48"
+else if val == "I" then "49"
+else if val == "J" then "4a"
+else if val == "K" then "4b"
+else if val == "L" then "4c"
+else if val == "M" then "4d"
+else if val == "N" then "4e"
+else if val == "O" then "4f"
+else if val == "P" then "50"
+else if val == "Q" then "51"
+else if val == "R" then "52"
+else if val == "S" then "53"
+else if val == "T" then "54"
+else if val == "U" then "55"
+else if val == "V" then "56"
+else if val == "W" then "57"
+else if val == "X" then "58"
+else if val == "Y" then "59"
+else if val == "Z" then "5a"
+else if val == "[" then "5b"
+else if val == "\\" then "5c"
+else if val == "]" then "5d"
+else if val == "^" then "5e"
+else if val == "_" then "5f"
+else if val == "`" then "60"
+else if val == "a" then "61"
+else if val == "b" then "62"
+else if val == "c" then "63"
+else if val == "d" then "64"
+else if val == "e" then "65"
+else if val == "f" then "66"
+else if val == "g" then "67"
+else if val == "h" then "68"
+else if val == "i" then "69"
+else if val == "j" then "6a"
+else if val == "k" then "6b"
+else if val == "l" then "6c"
+else if val == "m" then "6d"
+else if val == "n" then "6e"
+else if val == "o" then "6f"
+else if val == "p" then "70"
+else if val == "q" then "71"
+else if val == "r" then "72"
+else if val == "s" then "73"
+else if val == "t" then "74"
+else if val == "u" then "75"
+else if val == "v" then "76"
+else if val == "w" then "77"
+else if val == "x" then "78"
+else if val == "y" then "79"
+else if val == "z" then "7a"
+else if val == "{" then "7b"
+else if val == "|" then "7c"
+else if val == "}" then "7d"
+else if val == "~" then "7e"
+else if val == "" then "7f"
+else if val == "" then "80"
+else if val == "" then "81"
+else if val == "" then "82"
+else if val == "" then "83"
+else if val == "" then "84"
+else if val == "" then "85"
+else if val == "" then "86"
+else if val == "" then "87"
+else if val == "" then "88"
+else if val == "" then "89"
+else if val == "" then "8a"
+else if val == "" then "8b"
+else if val == "" then "8c"
+else if val == "" then "8d"
+else if val == "" then "8e"
+else if val == "" then "8f"
+else if val == "" then "90"
+else if val == "" then "91"
+else if val == "" then "92"
+else if val == "" then "93"
+else if val == "" then "94"
+else if val == "" then "95"
+else if val == "" then "96"
+else if val == "" then "97"
+else if val == "" then "98"
+else if val == "" then "99"
+else if val == "" then "9a"
+else if val == "" then "9b"
+else if val == "" then "9c"
+else if val == "" then "9d"
+else if val == "" then "9e"
+else if val == "" then "9f"
+else if val == "" then "a0"
+else if val == "" then "a1"
+else if val == "" then "a2"
+else if val == "" then "a3"
+else if val == "" then "a4"
+else if val == "" then "a5"
+else if val == "" then "a6"
+else if val == "" then "a7"
+else if val == "" then "a8"
+else if val == "" then "a9"
+else if val == "" then "aa"
+else if val == "" then "ab"
+else if val == "" then "ac"
+else if val == "" then "ad"
+else if val == "" then "ae"
+else if val == "" then "af"
+else if val == "" then "b0"
+else if val == "" then "b1"
+else if val == "" then "b2"
+else if val == "" then "b3"
+else if val == "" then "b4"
+else if val == "" then "b5"
+else if val == "" then "b6"
+else if val == "" then "b7"
+else if val == "" then "b8"
+else if val == "" then "b9"
+else if val == "" then "ba"
+else if val == "" then "bb"
+else if val == "" then "bc"
+else if val == "" then "bd"
+else if val == "" then "be"
+else if val == "" then "bf"
+else if val == "" then "c0"
+else if val == "" then "c1"
+else if val == "" then "c2"
+else if val == "" then "c3"
+else if val == "" then "c4"
+else if val == "" then "c5"
+else if val == "" then "c6"
+else if val == "" then "c7"
+else if val == "" then "c8"
+else if val == "" then "c9"
+else if val == "" then "ca"
+else if val == "" then "cb"
+else if val == "" then "cc"
+else if val == "" then "cd"
+else if val == "" then "ce"
+else if val == "" then "cf"
+else if val == "" then "d0"
+else if val == "" then "d1"
+else if val == "" then "d2"
+else if val == "" then "d3"
+else if val == "" then "d4"
+else if val == "" then "d5"
+else if val == "" then "d6"
+else if val == "" then "d7"
+else if val == "" then "d8"
+else if val == "" then "d9"
+else if val == "" then "da"
+else if val == "" then "db"
+else if val == "" then "dc"
+else if val == "" then "dd"
+else if val == "" then "de"
+else if val == "" then "df"
+else if val == "" then "e0"
+else if val == "" then "e1"
+else if val == "" then "e2"
+else if val == "" then "e3"
+else if val == "" then "e4"
+else if val == "" then "e5"
+else if val == "" then "e6"
+else if val == "" then "e7"
+else if val == "" then "e8"
+else if val == "" then "e9"
+else if val == "" then "ea"
+else if val == "" then "eb"
+else if val == "" then "ec"
+else if val == "" then "ed"
+else if val == "" then "ee"
+else if val == "" then "ef"
+else if val == "" then "f0"
+else if val == "" then "f1"
+else if val == "" then "f2"
+else if val == "" then "f3"
+else if val == "" then "f4"
+else if val == "" then "f5"
+else if val == "" then "f6"
+else if val == "" then "f7"
+else if val == "" then "f8"
+else if val == "" then "f9"
+else if val == "" then "fa"
+else if val == "" then "fb"
+else if val == "" then "fc"
+else if val == "" then "fd"
+else if val == "" then "fe"
+else if val == "" then "ff"
+else throw "Invalid character '${val}'."