Skip to content

Commit a798a47

Browse files
committed
v1.0.4: Update Cloudflare bypass + fix some issues
1 parent 8050100 commit a798a47

File tree

4 files changed

+81
-58
lines changed

4 files changed

+81
-58
lines changed

README.md

+12-4
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,23 @@ Now you have two options: the **script with a User Interface** *(like a true sof
3131

3232
### Starting the Script with a User Interface
3333
1. In this case you need to make sure that **tkinter** is available on your computer ([check here](https://github.com/BenSouchet/cw-wizard/blob/main/REQUIREMENTS.md#optional-needed-for-the-user-interface-script)).
34-
2. Open on your favorite browser one Cardmarket tab (just one and don't log-in).
34+
2. Open on your favorite browser one Cardmarket tab (just one).
3535
3. Execute this command in your terminal:
3636
```shell
3737
> python3 cw-wizard-gui.py
3838
```
3939
4. Then simply follow the steps in the interface that popped up.
4040

4141
### Starting the Command Line Script
42-
1. Open, on your favorite browser, one Cardmarket tab (just one, and don't log-in)
42+
1. Open, on your favorite browser, one Cardmarket tab (just one)
4343
2. Check the script help (to see available parameters):
4444
```shell
4545
> python3 cw-wizard.py -h
4646
```
4747
```text
4848
usage: CW Wizard [-h] [-v] -b BROWSER_NAME -u WANTLIST_URLS [WANTLIST_URLS ...] [-m MAX_SELLERS] [-w] [-c]
4949
50-
CW Wizard v1.0.3, Find the best bundles for the cards in your wantlist(s).
50+
CW Wizard v1.0.4, Find the best bundles for the cards in your wantlist(s).
5151
5252
options:
5353
-h, --help show this help message and exit
@@ -77,6 +77,10 @@ Example with multiple wantlists:
7777
4. If it's the first time you start the script your Cardmarket credentials will be asked to create a `credentials.json` file at the root of the project directory. For info on why credentials are required [read this section](https://github.com/BenSouchet/cw-wizard/edit/main/README.md#security-and-authentification).
7878
<img width="598" alt="credentials_dialog" src="https://user-images.githubusercontent.com/17025808/159022712-b95ef3f5-0da6-4547-8f94-d49c0a4582ee.png">
7979

80+
5. With your favorite browser search for "my user agent" on Google and copy the value (Should start with: `Mozilla/5.0`)
81+
82+
6. When requested paste the value and press enter.
83+
8084
5. It's all, if everything goes well a result HTML page will open on your default web browser, otherwise check the terminal to see the error message(s).
8185

8286
### Script Arguments
@@ -93,9 +97,13 @@ With the command line version of the script you can use the following arguments:
9397
| `-c` *OR* `--articles-comment` |If specified the script will retrieve and add sellers comments to the result page. | No |
9498

9599
## Version
96-
Current version is `1.0.3`, you can download this latest release on the Releases category (on the sidebar), from [this page](https://github.com/BenSouchet/cw-wizard/releases) or `git clone` the `main` branch of the repository.
100+
Current version is `1.0.4`, you can download this latest release on the Releases category (on the sidebar), from [this page](https://github.com/BenSouchet/cw-wizard/releases) or `git clone` the `main` branch of the repository.
97101

98102
## Changelog
103+
### 1.0.4
104+
- Update Cloudflare bypass, now require to specify your browser User-Agent to properly bypass retrictions.
105+
- Fix price retrieve due to recent modification in the Cardmarket interface.
106+
- Fix minor issues
99107

100108
### 1.0.3
101109
- Bypassing newly added Cloudflare protection by using user cookies (thanks to package **Browser Cookie 3**).

core.py

+16-27
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# Global variables
1919
SCRIPT_NAME = 'CW Wizard'
2020

21-
VERSION = '1.0.3'
21+
VERSION = '1.0.4'
2222

2323
EXIT_ERROR_MSG = 'The Wizard encountered issue(s) please check previous logs.\n'
2424
EXIT_SUCCESS_MSG = 'The Wizard has finish is work, have a great day!\n'
@@ -58,27 +58,10 @@
5858

5959
CREDENTIALS_PATH = Path.cwd().joinpath('credentials.json')
6060

61-
USER_AGENT_HEADER = {
62-
"Darwin": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
63-
"Windows": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
64-
"Linux": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
65-
}
66-
6761
REQUEST_HEADERS = {
68-
"authority": "cardmarket.com",
69-
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
70-
"accept-language": "fr,en-US;q=0.9,en;q=0.8",
71-
"cache-control": "max-age=0",
72-
"dnt": "1",
73-
"sec-ch-ua": '"Chromium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"',
74-
"sec-ch-ua-mobile": "?0",
75-
"sec-ch-ua-platform": '"macOS"',
76-
"sec-fetch-dest": "document",
77-
"sec-fetch-mode": "navigate",
78-
"sec-fetch-site": "none",
79-
"sec-fetch-user": "?1",
80-
"Upgrade-Insecure-Requests": "1",
81-
"User-Agent": USER_AGENT_HEADER[platform.system()]
62+
"accept": "*/*",
63+
"accept-language": "*",
64+
"dnt": "1"
8265
}
8366

8467

@@ -442,7 +425,7 @@ def load_more_articles(session, funct_result, soup, card, articles_table):
442425
while active:
443426
# Step 3.A: Get the price of the last card currently displayed
444427
last_article = articles_table.contents[-1]
445-
last_article_price_str = last_article.find('div', class_='price-container').find('span', class_='text-right').contents[0]
428+
last_article_price_str = last_article.find('div', class_='price-container').find('span', class_='text-nowrap').contents[0]
446429
last_article_price = Decimal(last_article_price_str.split(' ')[0].replace('.','').replace(',','.'))
447430

448431
# Step 3.B: Check if we need to load more articles, if yes send a new request to get more articles.
@@ -538,7 +521,7 @@ def populate_sellers_dict(session, sellers, wantlist, articles_comment=False, co
538521

539522
seller_profile_url = CARDMARKET_BASE_URL + seller_name_tag['href']
540523

541-
price_str = article_row.find('div', class_='price-container').find('span', class_='text-right').contents[0]
524+
price_str = article_row.find('div', class_='price-container').find('span', class_='text-nowrap').contents[0]
542525
price = Decimal(price_str.split(' ')[0].replace('.','').replace(',','.'))
543526
# Step 4.E: Check if price isn't above maxPrice
544527
if isinstance(card['maxPrice'], Decimal) and price > card['maxPrice']:
@@ -718,7 +701,7 @@ def get_cardmarket_cookies(browser_name):
718701
return browser_cookie3.load(domain_name=CARDMARKET_TOP_DOMAIN)
719702

720703

721-
def cardmarket_wantlist_wizard(browser_name, credentials, wantlist_urls, continue_on_warning, max_sellers, articles_comment=False):
704+
def cardmarket_wantlist_wizard(browser_name, user_agent, credentials, wantlist_urls, continue_on_warning, max_sellers, articles_comment=False):
722705
funct_result = FunctResult()
723706
LOG.debug('------- Calling the Wizard...\r\n')
724707

@@ -730,7 +713,11 @@ def cardmarket_wantlist_wizard(browser_name, credentials, wantlist_urls, continu
730713
with requests.Session() as session:
731714
# Setting cookies and headers to bypass / skip Cloudflare protection
732715
session.cookies = get_cardmarket_cookies(browser_name)
733-
session.headers.update(REQUEST_HEADERS)
716+
headers = REQUEST_HEADERS.copy()
717+
headers["user-agent"] = str(user_agent)
718+
print(headers)
719+
print(session.cookies)
720+
session.headers.update(headers)
734721

735722
# Step 2: Log-in to Cardmarket
736723
funct_result = cardmarket_log_in(session, credentials)
@@ -893,14 +880,16 @@ def create_credentials_file(credentials, silently=False):
893880
return True
894881

895882

896-
def check_credentials_validity(browser_name, credentials, silently=False):
883+
def check_credentials_validity(browser_name, user_agent, credentials, silently=False):
897884
funct_result = FunctResult()
898885

899886
# Step 1: Create a web session
900887
with requests.Session() as session:
901888
# Setting cookies and headers to bypass / skip Cloudflare protection
902889
session.cookies = get_cardmarket_cookies(browser_name)
903-
session.headers.update(REQUEST_HEADERS)
890+
headers = REQUEST_HEADERS.copy()
891+
headers["user-agent"] = str(user_agent)
892+
session.headers.update(headers)
904893

905894
# Step 2: Log-in to Cardmarket
906895
funct_result = cardmarket_log_in(session, credentials, silently=silently)

cw-wizard-gui.py

+26-13
Original file line numberDiff line numberDiff line change
@@ -141,19 +141,19 @@ def close_window(window):
141141
window.root.destroy()
142142

143143

144-
def wizard_wrapper(credentials, wantlist_urls, max_sellers):
144+
def wizard_wrapper(browser_name, user_agent, credentials, wantlist_urls, max_sellers):
145145
# Step 1: Call the Wizard
146-
result = cardmarket_wantlist_wizard(credentials, wantlist_urls, continue_on_warning=True, max_sellers=max_sellers)
146+
result = cardmarket_wantlist_wizard(browser_name, user_agent, credentials, wantlist_urls, continue_on_warning=True, max_sellers=max_sellers)
147147
result.logMessages()
148148

149149
# Step 2: Push result into the thread queue
150150
global THREAD_QUEUE
151151
THREAD_QUEUE.put(result)
152152

153153

154-
def credentials_validity_wrapper(browser_name, credentials):
154+
def credentials_validity_wrapper(browser_name, user_agent, credentials):
155155
# Step 1: Check the validity
156-
result = check_credentials_validity(browser_name, credentials, silently=True)
156+
result = check_credentials_validity(browser_name, user_agent, credentials, silently=True)
157157
result.logMessages()
158158

159159
# Step 2: If valid create the file to avoid the user to enter them again
@@ -206,7 +206,7 @@ def credentials_validity_has_finished(params):
206206

207207

208208
def next_step(window, param1, param2):
209-
if window.step == 'request_browser_name':
209+
if window.step == 'request_browser_name_user_agent':
210210
# Step 1: Check browser name validity
211211
result = get_formatted_browser_name(param1.get())
212212
result.logMessages()
@@ -217,6 +217,13 @@ def next_step(window, param1, param2):
217217
# Set the reformatted browser_name
218218
window.browser_name = result.getResult()
219219

220+
# Step 1.B: Handling User-Agent
221+
user_agent = param2.get()
222+
if not user_agent:
223+
set_window_description(window, 'Error: User-Agent cannot be empty.', is_error=True)
224+
return True
225+
window.user_agent = user_agent
226+
220227
# Step 2: Retrieve credentials and check validity
221228
result = get_credentials_from_file()
222229
result.logMessages()
@@ -227,7 +234,7 @@ def next_step(window, param1, param2):
227234
if 'skip-check' not in credentials:
228235
# Step 2.B: Cardmarket connexion test in a thread
229236
window_wait_screen(window)
230-
thread = threading.Thread(target=credentials_validity_wrapper, args=(window.browser_name, credentials,), daemon=True)
237+
thread = threading.Thread(target=credentials_validity_wrapper, args=(window.browser_name, window.user_agent, credentials,), daemon=True)
231238
thread.start()
232239

233240
# Step 2.C: Callback to move to the next step
@@ -254,7 +261,7 @@ def next_step(window, param1, param2):
254261

255262
# Step 2.B: Cardmarket connexion test in a thread
256263
window_wait_screen(window)
257-
thread = threading.Thread(target=credentials_validity_wrapper, args=(window.browser_name, credentials,), daemon=True)
264+
thread = threading.Thread(target=credentials_validity_wrapper, args=(window.browser_name, window.user_agent, credentials,), daemon=True)
258265
thread.start()
259266

260267
# Step 3: Callback to move to the next step
@@ -285,7 +292,7 @@ def next_step(window, param1, param2):
285292
# Step 3: All seems good, display the waiting screen and call the wizard !
286293
window.step = 'wizard_in_progress'
287294
window_wait_screen(window)
288-
thread = threading.Thread(target=wizard_wrapper, args=(window.credentials, wantlist_urls, max_sellers,), daemon=True)
295+
thread = threading.Thread(target=wizard_wrapper, args=(window.browser_name, window.user_agent, window.credentials, wantlist_urls, max_sellers,), daemon=True)
289296
thread.start()
290297

291298
# Step 4: Callback to display the final screen when the wizard has finished
@@ -336,9 +343,9 @@ def set_window_description(window, message, is_error=False, is_warn=False):
336343
window.description.set(message)
337344

338345

339-
def window_request_browser_name(window):
346+
def window_request_browser_name_user_agent(window):
340347
# Step 1: Set current prog step
341-
window.step = 'request_browser_name'
348+
window.step = 'request_browser_name_user_agent'
342349

343350
# Step 2: Set title and description
344351
window.title.set('Open a Cardmarket tab in your favorite browser')
@@ -357,15 +364,21 @@ def window_request_browser_name(window):
357364

358365
# Step 3.C: Create the variable that will store the browser name
359366
browser_name = tkinter.StringVar()
367+
user_agent = tkinter.StringVar()
360368

361369
# Step 3.D: Create the two row
362370
label_browser_name = create_label(frame_parent, 'Browser Name', 'content', window.content_bg_color)
363371
label_browser_name.grid(row=0, column=0, pady=(0, 4), sticky=tkinter.E)
364372
entry_browser_name = create_entry(frame_parent, browser_name, window.content_bg_color)
365373
entry_browser_name.grid(row=0, column=1, ipadx=4, padx=(4, 0), pady=(0, 4), sticky=tkinter.NSEW)
366374

375+
label_user_agent = create_label(frame_parent, 'User-Agent', 'content', window.content_bg_color)
376+
label_user_agent.grid(row=1, column=0, sticky=tkinter.E)
377+
entry_user_agent = create_entry(frame_parent, user_agent, window.content_bg_color)
378+
entry_user_agent.grid(row=1, column=1, padx=(4, 0), sticky=tkinter.NSEW)
379+
367380
# Step 4: Add disclaimer message
368-
text_disclaimer = "Disclaimer: Ensure only one Cardmarket tab is open and you aren't logged-in."
381+
text_disclaimer = "Disclaimer: Ensure only one Cardmarket tab is open and you aren't logged-in.\nFor User-Agent search \"my user agent\" on Google."
369382
label_disclaimer = create_label(window.content, text_disclaimer, 'description', window.content_bg_color)
370383
label_disclaimer.grid(row=1, column=0, pady=(0, 14), sticky=tkinter.S)
371384

@@ -376,7 +389,7 @@ def window_request_browser_name(window):
376389
# Step 6: Update Next button
377390
window.button_next_text.set('NEXT STEP')
378391
window.button_next.configure(state=tkinter.NORMAL)
379-
window.button_next.configure(command=partial(next_step, window, browser_name, None))
392+
window.button_next.configure(command=partial(next_step, window, browser_name, user_agent))
380393

381394

382395
def window_request_credentials(window, error_msg=None):
@@ -608,7 +621,7 @@ def main():
608621
window = create_window()
609622

610623
# Step 2: Request the browser name used
611-
window_request_browser_name(window)
624+
window_request_browser_name_user_agent(window)
612625

613626
# Step 4: Put back the alpha of the window to full opaque
614627
window.root.attributes('-alpha', 1.0)

cw-wizard.py

+27-14
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,26 @@ def get_credentials_user_inputs():
3333
return funct_result
3434

3535
# Step 2: Ask for the login
36-
credentials['login'] = input('Enter your Cardmarket login: ')
36+
credentials['login'] = input('\nEnter your Cardmarket login: ')
3737

3838
# Step 2: Ask for the login
39-
credentials['password'] = getpass.getpass('Enter your Cardmarket password: ')
39+
credentials['password'] = getpass.getpass('\nEnter your Cardmarket password: ')
40+
41+
print("\n")
4042

4143
create_credentials_file(credentials)
4244

4345
funct_result.addResult(credentials)
4446

4547
return funct_result
4648

49+
def get_user_agent_from_inputs():
50+
funct_result = FunctResult()
51+
LOG.info("To properly bypass Cloudflare your user agent is require.")
52+
funct_result.addResult(input('\nEnter your browser User-Agent (search on Google "my user agent"):\n'))
53+
print("\n")
54+
return funct_result
55+
4756
def main(browser_name, wantlist_urls, continue_on_warning, max_sellers, articles_comment):
4857
"""Entry point of the CW Wizard script"""
4958

@@ -68,28 +77,32 @@ def main(browser_name, wantlist_urls, continue_on_warning, max_sellers, articles
6877

6978
credentials = None
7079
if result.isValid():
71-
# Check the credentials are valid
7280
credentials = result.getResult()
81+
82+
result = get_user_agent_from_inputs()
83+
user_agent = result.getResult()
84+
85+
if credentials:
86+
# Check the credentials are valid
7387
if 'skip-check' not in credentials:
74-
result = check_credentials_validity(browser_name, credentials, silently=True)
88+
result = check_credentials_validity(browser_name, user_agent, credentials, silently=True)
7589
result.logMessages()
76-
# Since we have maybe check the validity, perform a second check on the result object
77-
if result.isValid():
78-
credentials = result.getResult()
79-
80-
if not credentials:
90+
if result.isValid():
91+
credentials = result.getResult()
92+
else:
93+
return LOG.error(EXIT_ERROR_MSG)
94+
else:
8195
# File not found, ask the user if he want to create the file
8296
result = get_credentials_user_inputs()
8397
result.logMessages()
8498

8599
if result.isValid():
86100
credentials = result.getResult()
101+
else:
102+
return LOG.error(EXIT_ERROR_MSG)
87103

88-
if not credentials:
89-
return LOG.error(EXIT_ERROR_MSG)
90-
91-
# Step 3: Call the Wizard
92-
result = cardmarket_wantlist_wizard(browser_name, credentials, wantlist_urls, continue_on_warning=continue_on_warning, max_sellers=max_sellers, articles_comment=articles_comment)
104+
# Step 3: Call the Wizards
105+
result = cardmarket_wantlist_wizard(browser_name, user_agent, credentials, wantlist_urls, continue_on_warning=continue_on_warning, max_sellers=max_sellers, articles_comment=articles_comment)
93106
result.logMessages()
94107

95108
if not result.isValid():

0 commit comments

Comments
 (0)