11"""
2- This script is a Selenium bot that redeems a gift code
3- for the mobile game Whiteout Survival by using their website
2+ This script redeems a gift code for players of the mobile game
3+ Whiteout Survival by using their API
44
55It requires an input file that contains all player IDs and
66tracks its progress in an output file to be able to continue
77in case it runs into errors without retrying to redeem a code
88for everyone
9-
10- This script currently only supports Mac OS because it is based
11- on the Safari browser
129"""
1310import argparse
11+ import hashlib
1412import json
1513import sys
14+ import time
1615
17- from selenium .webdriver .support import ui
18- from selenium import webdriver
19- from selenium .common .exceptions import TimeoutException
20- from selenium .webdriver .common .by import By
21-
22-
23- def save_results (filename , results_to_save ):
24- """
25- This function was needed to make sure we can save progress on TimeoutExceptions
26- """
27- with open (filename , 'w' , encoding = "utf-8" ) as fp :
28- json .dump (results_to_save , fp )
29-
16+ import requests
3017
3118# Handle arguments the script is called with
3219parser = argparse .ArgumentParser ()
@@ -35,7 +22,7 @@ def save_results(filename, results_to_save):
3522 dest = 'player_file' , default = 'player.json' )
3623parser .add_argument ('-r' , '--results-file' ,
3724 dest = 'results_file' , default = 'results.json' )
38- parser .add_argument ('--restart' , type = bool , dest = 'restart' , default = False )
25+ parser .add_argument ('--restart' , dest = 'restart' , action = "store_true" )
3926args = parser .parse_args ()
4027
4128# Open and read the user files
@@ -58,101 +45,89 @@ def save_results(filename, results_to_save):
5845else :
5946 result = found_item
6047
61- # Setup Selenium
62- URL = "https://wos-giftcode.centurygame.com"
63- driver = webdriver .Safari ()
64- driver .get (URL )
65- wait = ui .WebDriverWait (driver , 30 )
66-
6748# Some variables that are used to tracking progress
6849session_counter = 1
6950counter_successfully_claimed = 0
7051counter_already_claimed = 0
7152counter_error = 0
7253
54+ URL = "https://wos-giftcode-api.centurygame.com/api"
55+ # The salt is appended to the string that is then signed using md5 and sent as part of the request
56+ SALT = "tB87#kPtkxqOS2"
57+ HTTP_HEADER = {"Content-Type" : "application/x-www-form-urlencoded" ,
58+ "Accept" : "application/json" }
59+
60+ i = 0
7361for player in players :
7462
63+ # Print progress bar
64+ i += 1
65+
66+ print ("\x1b [K" + str (i ) + "/" + str (len (players )) +
67+ " complete. Redeeming for " + player ["original_name" ], end = "\r " , flush = True )
68+
7569 # Check if the code has been redeemed for this player already
7670 # Continue to the next iteration if it has been
77- if result ["status" ].get (player ["id" ]) == "Successful" :
71+ if result ["status" ].get (player ["id" ]) == "Successful" and not args .restart :
72+ counter_already_claimed += 1
7873 continue
7974
8075 # This is necessary because we reload the page every 5 players
8176 # and the website isn't sometimes ready before we continue
82- try :
83- wait .until (lambda driver : driver .find_element (
84- By .XPATH , "//input[contains(@placeholder,'Player ID')]" ))
85- except TimeoutException as e :
86- print ("Timeout Exception" )
87- save_results (args .results_file , results )
77+ request_data = {"fid" : player ["id" ], "time" : time .time_ns ()}
78+ request_data ["sign" ] = hashlib .md5 (("fid=" + request_data ["fid" ] + "&time=" + str (
79+ request_data ["time" ]) + SALT ).encode ("utf-8" )).hexdigest ()
80+
81+ # Login the player
82+ # It is enough to send the POST request, we don't need to store any cookies/session tokens
83+ # to authenticate during the next request
84+ login_request = requests .post (
85+ URL + '/player' , data = request_data , headers = HTTP_HEADER , timeout = 30 )
86+ login_response = login_request .json ()
87+ if login_response ["msg" ] != "success" :
88+ print ("Login not possible" )
8889 sys .exit (1 )
8990
90- # Enter the player ID
91- player_id_input = driver .find_element (
92- By .XPATH , "//input[contains(@placeholder,'Player ID')]" )
93- player_id_input .clear ()
94- player_id_input .send_keys (player ["id" ])
95-
96- # Login the player by using the login button
97- # We are using the Selenium wait feature to make sure the player is logged in before we continue
98- # We know the player is logged in, once the exit icon appears
99- login_button = driver .find_element (By .CLASS_NAME , "login_btn" ).click ()
100- try :
101- wait .until (lambda driver : driver .find_element (
102- By .CLASS_NAME , "exit_icon" ))
103- except TimeoutException as e :
104- print ("Timeout Exception" )
105- save_results (args .results_file , results )
106- sys .exit (1 )
91+ # Create the request data that contains the signature and the code
92+ request_data ["cdk" ] = args .code
93+ request_data ["sign" ] = hashlib .md5 (("cdk=" + request_data ["cdk" ] + \
94+ "&fid=" + request_data ["fid" ] + \
95+ "&time=" + str (request_data ["time" ]) + \
96+ SALT ).encode ("utf-8" )).hexdigest ()
10797
108- # Now we record the login name for later
109- player ["name" ] = driver .find_element (By .CLASS_NAME , "name" ).text
110-
111- # Enter the gift code and hit confirm
112- # We again wait until the request is sent before we continue
113- gift_code_input = driver .find_element (
114- By .XPATH , "//input[contains(@placeholder,'Enter Gift Code')]" )
115- gift_code_input .clear ()
116- gift_code_input .send_keys (args .code )
117- redeem_button = driver .find_element (By .CLASS_NAME , "exchange_btn" ).click ()
118- try :
119- wait .until (lambda driver : driver .find_element (
120- By .CLASS_NAME , "confirm_btn" ))
121- except TimeoutException as e :
122- print ("Timeout Exception" )
123- save_results (args .results_file , results )
124- sys .exit (1 )
125- player ["status" ] = driver .find_element (By .CLASS_NAME , "msg" ).text
98+ # Send the gif code redemption request
99+ redeem_request = requests .post (
100+ URL + '/gift_code' , data = request_data , headers = HTTP_HEADER , timeout = 30 )
101+ redeem_response = redeem_request .json ()
126102
127103 # In case the gift code is broken, exit straight away
128- if player [ "status " ] == "Gift Code not found!" :
129- print ("The gift code doesn't exist!" )
104+ if redeem_response [ "err_code " ] == 40014 :
105+ print ("\n The gift code doesn't exist!" )
130106 sys .exit (1 )
131- elif player [ "status " ] == "Expired, unable to claim." :
132- print ("The gift code is expired!" )
107+ elif redeem_response [ "err_code " ] == 40007 :
108+ print ("\n The gift code is expired!" )
133109 sys .exit (1 )
134- elif player [ "status " ] == "Already claimed, unable to claim again." :
110+ elif redeem_response [ "err_code " ] == 40008 : # ALREADY CLAIMED
135111 counter_already_claimed += 1
136112 result ["status" ][player ["id" ]] = "Successful"
137- elif player [ "status " ] == "Redeemed, please claim the rewards in your mail!" :
113+ elif redeem_response [ "err_code " ] == 20000 : # SUCCESSFULLY CLAIMED
138114 counter_successfully_claimed += 1
139115 result ["status" ][player ["id" ]] = "Successful"
116+ elif redeem_response ["err_code" ] == 40004 : # TIMEOUT RETRY
117+ result ["status" ][player ["id" ]] = "Unsuccessful"
140118 else :
141119 result ["status" ][player ["id" ]] = "Unsuccessful"
120+ print ("\n Error occurred: " + str (redeem_response ))
142121 counter_error += 1
143122
144- driver .find_element (By .CLASS_NAME , "confirm_btn" ).click ()
145-
146- # Now we log the user out again before we continue with the next one
147- driver .find_element (By .CLASS_NAME , "exit_icon" ).click ()
148-
149123 # Refresh the webpage every 5 players to avoid getting soft-banned at some point
150124 if session_counter % 5 == 0 :
151- driver . refresh ( )
125+ time . sleep ( 5 )
152126
153127 session_counter += 1
154128
155- save_results (args .results_file , results )
129+ with open (args .results_file , 'w' , encoding = "utf-8" ) as fp :
130+ json .dump (results , fp )
156131
157132# Print general stats
158133print ("\n Successfully claimed gift code for " + str (counter_successfully_claimed ) + " players.\n " +
0 commit comments