Skip to content

Commit

Permalink
fix: resolve KeyError in getEdgeVersions and bug in getLanguageCountry (
Browse files Browse the repository at this point in the history
#237)

1. keyError in getEdgeVersions
Looks like users "randomly" get response json with capitalized or
uncapitalized key.
Thus, add temp function to ignore the capitalization.

3. Key Error in `getLanguageCountry()` country in config.yaml is using
key 'geolocation', while it's fetched by the wrong key 'location'
```yaml
default:
  geolocation: US # Replace with your country code https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
```
```python
@staticmethod
def getLanguageCountry(language: str, country: str) -> tuple[str, str]:
    if not country:
        country = CONFIG.get("default").get("location")
```
  • Loading branch information
cal4 authored Dec 10, 2024
2 parents 2bf51d3 + 7974152 commit 4ff9d79
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 17 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,4 @@ runbot.bat
/google_trends.dir
/google_trends.bak
/config-private.yaml
mypy.ini
141 changes: 141 additions & 0 deletions activities.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import contextlib
import random
import time

from selenium.common import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement

from src.browser import Browser


class Activities:
def __init__(self, browser: Browser):
self.browser = browser
self.webdriver = browser.webdriver

def openDailySetActivity(self, cardId: int):
# Open the Daily Set activity for the given cardId
element = self.webdriver.find_element(By.XPATH,
f'//*[@id="daily-sets"]/mee-card-group[1]/div/mee-card[{cardId}]/div/card-content/mee-rewards-daily-set-item-content/div/a', )
self.browser.utils.click(element)
self.browser.utils.switchToNewTab(timeToWait=8)

def openMorePromotionsActivity(self, cardId: int):
# Open the More Promotions activity for the given cardId
element = self.webdriver.find_element(By.CSS_SELECTOR,
f"#more-activities > .m-card-group > .ng-scope:nth-child({cardId + 1}) .ds-card-sec")
self.browser.utils.click(element)
self.browser.utils.switchToNewTab(timeToWait=5)

def completeSearch(self):
# Simulate completing a search activity
time.sleep(random.randint(10, 15))
self.browser.utils.closeCurrentTab()

def completeSurvey(self):
# Simulate completing a survey activity
# noinspection SpellCheckingInspection
self.webdriver.find_element(By.ID, f"btoption{random.randint(0, 1)}").click()
time.sleep(random.randint(10, 15))
self.browser.utils.closeCurrentTab()

def completeQuiz(self):
# Simulate completing a quiz activity
with contextlib.suppress(TimeoutException):
startQuiz = self.browser.utils.waitUntilQuizLoads()
self.browser.utils.click(startQuiz)
self.browser.utils.waitUntilVisible(
By.ID, "overlayPanel", 5
)
currentQuestionNumber: int = self.webdriver.execute_script(
"return _w.rewardsQuizRenderInfo.currentQuestionNumber"
)
maxQuestions = self.webdriver.execute_script(
"return _w.rewardsQuizRenderInfo.maxQuestions"
)
numberOfOptions = self.webdriver.execute_script(
"return _w.rewardsQuizRenderInfo.numberOfOptions"
)
for _ in range(currentQuestionNumber, maxQuestions + 1):
if numberOfOptions == 8:
answers = []
for i in range(numberOfOptions):
isCorrectOption = self.webdriver.find_element(
By.ID, f"rqAnswerOption{i}"
).get_attribute("iscorrectoption")
if isCorrectOption and isCorrectOption.lower() == "true":
answers.append(f"rqAnswerOption{i}")
for answer in answers:
element = self.webdriver.find_element(By.ID, answer)
self.browser.utils.click(element)
self.browser.utils.waitUntilQuestionRefresh()
elif numberOfOptions in [2, 3, 4]:
correctOption = self.webdriver.execute_script(
"return _w.rewardsQuizRenderInfo.correctAnswer"
)
for i in range(numberOfOptions):
if (
self.webdriver.find_element(
By.ID, f"rqAnswerOption{i}"
).get_attribute("data-option")
== correctOption
):
element = self.webdriver.find_element(By.ID, f"rqAnswerOption{i}")
self.browser.utils.click(element)

self.browser.utils.waitUntilQuestionRefresh()
break
self.browser.utils.closeCurrentTab()

def completeABC(self):
# Simulate completing an ABC activity
counter = self.webdriver.find_element(
By.XPATH, '//*[@id="QuestionPane0"]/div[2]'
).text[:-1][1:]
numberOfQuestions = max(int(s) for s in counter.split() if s.isdigit())
for question in range(numberOfQuestions):
element = self.webdriver.find_element(By.ID, f"questionOptionChoice{question}{random.randint(0, 2)}")
self.browser.utils.click(element)
time.sleep(random.randint(10, 15))
element = self.webdriver.find_element(By.ID, f"nextQuestionbtn{question}")
self.browser.utils.click(element)
time.sleep(random.randint(10, 15))
time.sleep(random.randint(1, 7))
self.browser.utils.closeCurrentTab()

def completeThisOrThat(self):
# Simulate completing a This or That activity
startQuiz = self.browser.utils.waitUntilQuizLoads()
self.browser.utils.click(startQuiz)
self.browser.utils.waitUntilVisible(
By.XPATH, '//*[@id="currentQuestionContainer"]/div/div[1]', 10
)
time.sleep(random.randint(10, 15))
for _ in range(10):
correctAnswerCode = self.webdriver.execute_script(
"return _w.rewardsQuizRenderInfo.correctAnswer"
)
answer1, answer1Code = self.getAnswerAndCode("rqAnswerOption0")
answer2, answer2Code = self.getAnswerAndCode("rqAnswerOption1")
answerToClick: WebElement
if answer1Code == correctAnswerCode:
answerToClick = answer1
elif answer2Code == correctAnswerCode:
answerToClick = answer2

self.browser.utils.click(answerToClick)
time.sleep(random.randint(10, 15))

time.sleep(random.randint(10, 15))
self.browser.utils.closeCurrentTab()

def getAnswerAndCode(self, answerId: str) -> tuple[WebElement, str]:
# Helper function to get answer element and its code
answerEncodeKey = self.webdriver.execute_script("return _G.IG")
answer = self.webdriver.find_element(By.ID, answerId)
answerTitle = answer.get_attribute("data-option")
return (
answer,
self.browser.utils.getAnswerCode(answerEncodeKey, answerTitle),
)
1 change: 1 addition & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ apprise:
summary: ON_ERROR
default:
geolocation: US # Replace with your country code https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2
language: en # Replace with your language code https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
logging:
level: INFO # See https://docs.python.org/3/library/logging.html#logging-levels
retries:
Expand Down
13 changes: 8 additions & 5 deletions src/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from src import Account, RemainingSearches
from src.userAgentGenerator import GenerateUserAgent
from src.utils import Utils, CONFIG, saveBrowserConfig, getProjectRoot, getBrowserConfig
from src.utils import CONFIG, Utils, getBrowserConfig, getProjectRoot, saveBrowserConfig


class Browser:
Expand Down Expand Up @@ -102,7 +102,7 @@ def browserSetup(
options.add_argument("--disable-features=PrivacySandboxSettings4")
options.add_argument("--disable-http2")
options.add_argument("--disable-search-engine-choice-screen") # 153
options.page_load_strategy = 'eager'
options.page_load_strategy = "eager"

seleniumwireOptions: dict[str, Any] = {"verify_ssl": False}

Expand Down Expand Up @@ -223,19 +223,22 @@ def setupProfiles(self) -> Path:
@staticmethod
def getLanguageCountry(language: str, country: str) -> tuple[str, str]:
if not country:
country = CONFIG.get("default").get("location")
country = CONFIG.get("default").get("geolocation")

if not language:
country = CONFIG.get("default").get("language")

if not language or not country:
currentLocale = locale.getlocale()
if not language:
with contextlib.suppress(ValueError):
language = pycountry.languages.get(
name=currentLocale[0].split("_")[0]
alpha_2=currentLocale[0].split("_")[0]
).alpha_2
if not country:
with contextlib.suppress(ValueError):
country = pycountry.countries.get(
name=currentLocale[0].split("_")[1]
alpha_2=currentLocale[0].split("_")[1]
).alpha_2

if not language or not country:
Expand Down
30 changes: 23 additions & 7 deletions src/userAgentGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,29 +139,45 @@ def getEdgeVersions(self) -> tuple[str, str]:
response = self.getWebdriverPage(
"https://edgeupdates.microsoft.com/api/products"
)

def get_value_ignore_case(data: dict, key: str) -> Any:
"""Get the value from a dictionary ignoring the case of the first letter of the key."""
for k, v in data.items():
if k.lower() == key.lower():
return v
return None

data = response.json()
if stableProduct := next(
(product for product in data if product["product"] == "Stable"),
(
product
for product in data
if get_value_ignore_case(product, "product") == "Stable"
),
None,
):
releases = stableProduct["releases"]
releases = get_value_ignore_case(stableProduct, "releases")
androidRelease = next(
(release for release in releases if release["platform"] == "Android"),
(
release
for release in releases
if get_value_ignore_case(release, "platform") == "Android"
),
None,
)
windowsRelease = next(
(
release
for release in releases
if release["platform"] == "Windows"
and release["architecture"] == "x64"
if get_value_ignore_case(release, "platform") == "Windows"
and get_value_ignore_case(release, "architecture") == "x64"
),
None,
)
if androidRelease and windowsRelease:
return (
windowsRelease["productVersion"],
androidRelease["productVersion"],
get_value_ignore_case(windowsRelease, "productVersion"),
get_value_ignore_case(androidRelease, "productVersion"),
)
raise HTTPError("Failed to get Edge versions.")

Expand Down
9 changes: 4 additions & 5 deletions src/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
from requests import Session
from requests.adapters import HTTPAdapter
from selenium.common import (
NoSuchElementException,
TimeoutException,
ElementClickInterceptedException,
ElementNotInteractableException,
NoSuchElementException,
TimeoutException,
)
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.common.by import By
Expand All @@ -28,8 +28,7 @@
from selenium.webdriver.support.wait import WebDriverWait
from urllib3 import Retry

from .constants import REWARDS_URL
from .constants import SEARCH_URL
from .constants import REWARDS_URL, SEARCH_URL

DEFAULT_CONFIG: MappingProxyType = MappingProxyType(
{
Expand Down Expand Up @@ -277,7 +276,7 @@ def sendNotification(title: str, body: str, e: Exception = None) -> None:
return
for url in urls:
apprise.add(url)
assert apprise.notify(title=str(title), body=str(body))
# assert apprise.notify(title=str(title), body=str(body)) # not work for telegram


def getAnswerCode(key: str, string: str) -> str:
Expand Down

1 comment on commit 4ff9d79

@SteveTryndamere
Copy link

@SteveTryndamere SteveTryndamere commented on 4ff9d79 Dec 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    @staticmethod
    def getLanguageCountry(language: str, country: str) -> tuple[str, str]:
        if not country:
            country = CONFIG.get("default").get("geolocation")
        if not language:
-            country = CONFIG.get("default").get("language") 
+           language = CONFIG.get("default").get("language") # should be assigned to language instead of overwriting country !!!!!

this will cause assertion error in getGoogleTrends, where
status_code=400, requests.codes.ok=200
"400 Bad Request: This HTTP status code indicates that the server could not understand the request due to invalid syntax. The client should not repeat the request without modifications."

refer to issue #246

Please sign in to comment.