Skip to content

Commit

Permalink
1.0.0 (#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
cal4 authored Aug 25, 2024
2 parents 51bbbcc + e3f1d65 commit 54e00cb
Show file tree
Hide file tree
Showing 19 changed files with 273 additions and 162 deletions.
9 changes: 0 additions & 9 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
name: Bug report
description: Make sure you check if you are purposefully causing an error! (bad installation, etc.)
title: "Title"
labels: [ "bug" ]
body:

Expand All @@ -16,14 +15,6 @@ body:
- label: |
I've cleared the sessions folder.
required: true
- type: checkboxes
id: title
attributes:
label: Title
options:
- label: |
The title is no longer "Title" and I edited it with the right error name.
required: true
- type: dropdown
id: branch
attributes:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,11 @@ pyrightconfig.json
# Custom rules (everything added below won't be overridden by 'Generate .gitignore File' if you use 'Update' option)

accounts.json
config.yaml
sessions
logs
runbot.bat
.DS_Store
/google_trends.dat
/google_trends.dir
/google_trends.bak
/config-private.yaml
5 changes: 5 additions & 0 deletions .template-config-private.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# config-private.yaml
# Copy this file to config-private.yaml to use
apprise:
urls:
- 'discord://WebhookID/WebhookToken' # Replace with your actual Apprise service URLs
42 changes: 41 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,45 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2024-08-23

### Removed

- `apprise.urls` from [config.yaml](config.yaml)
- This now lives in `config-private.yaml`, see [.template-config-private.yaml](.template-config-private.yaml) on how
to configure
- This prevents accidentally leaking sensitive information since `config-private.yaml` is .gitignore'd

### Added

- Support for automatic handling of logins with 2FA and for passwordless setups:
- Passwordless login is supported in both visible and headless mode by displaying the code that the user has to select
on their phone in the terminal window
- 2FA login with TOTPs is supported in both visible and headless mode by allowing the user to provide their TOTP key
in `accounts.json` which automatically generates the one time password
- 2FA login with device-based authentication is supported in theory, BUT doesn't currently work as the undetected
chromedriver for some reason does not receive the confirmation signal after the user approves the login
- Completing quizzes started but not completed in previous runs
- Promotions/More activities
- Find places to stay
- How's the economy?
- Who won?
- Gaming time

### Changed

- Incomplete promotions Apprise notifications
- How incomplete promotions are determined
- Batched into single versus multiple notifications
- Full exception is sent via Apprise versus just error message

### Fixed

- Promotions/More activities
- Too tired to cook tonight?
- [Last searches always timing out](https://github.com/klept0/MS-Rewards-Farmer/issues/172)
- [Quizzes don't complete](https://github.com/klept0/MS-Rewards-Farmer/issues)

## [0.2.1] - 2024-08-13

### Fixed
Expand All @@ -20,7 +59,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Allows users to choose between Miniconda, Anaconda, and Local Python
- Prompts users to input the name of their environment (if using Miniconda or Anaconda)
- Uses the script directory as the output path
- Default trigger time is set to 6:00 AM on a specified day, with instructions to modify settings after importing to Task Scheduler
- Default trigger time is set to 6:00 AM on a specified day, with instructions to modify settings after importing to
Task Scheduler
- Includes a batch file (`MS_reward.bat`) for automatic execution of the Python script

### Fixed
Expand Down
29 changes: 17 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,23 +47,28 @@

4. Edit the `accounts.json.sample` with your accounts credentials and rename it by removing `.sample` at the end.

The "totp" field is not mandatory, only enter your TOTP key if you use it for 2FA (if ommitting, don't keep
it as an empty string, remove the line completely).

The "proxy" field is not mandatory, you can omit it if you don't want to use proxy (don't keep it as an empty string,
remove the line completely).

- If you want to add more than one account, the syntax is the following:

```json
[
{
"username": "Your Email 1",
"password": "Your Password 1",
"proxy": "http://user:pass@host1:port"
},
{
"username": "Your Email 2",
"password": "Your Password 2",
"proxy": "http://user:pass@host2:port"
}
{
"username": "Your Email 1",
"password": "Your Password 1",
"totp": "0123 4567 89ab cdef",
"proxy": "http://user:pass@host1:port"
},
{
"username": "Your Email 2",
"password": "Your Password 2",
"totp": "0123 4567 89ab cdef",
"proxy": "http://user:pass@host2:port"
}
]
```

Expand All @@ -89,8 +94,8 @@
`(ex: http://user:pass@host:port)`
- `-cv/--chromeversion` to use a specific version of chrome
`(ex: 118)`
- `-da/--disable-apprise` to disable Apprise notification, overriding [config.yaml](config.yaml). Useful when running
manually as opposed to on a schedule.
- `-da/--disable-apprise` disables Apprise notifications for the session, overriding [config.yaml](config.yaml).
Useful when running manually as opposed to on a schedule.
- `-t/--searchtype` to only do `desktop` or `mobile` searches, `(ex: --searchtype=mobile)`

## Features
Expand Down
2 changes: 2 additions & 0 deletions accounts.json.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
{
"username": "Your Email 1",
"password": "Your Password 1",
"totp": "0123 4567 89ab cdef",
"proxy": "http://user:pass@host1:port"
},
{
"username": "Your Email 2",
"password": "Your Password 2",
"totp": "0123 4567 89ab cdef",
"proxy": "http://user:pass@host2:port"
}
]
3 changes: 0 additions & 3 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
# config.yaml
apprise:
summary: ON_ERROR
urls:
- 'discord://WebhookID/WebhookToken' # Replace with your actual Apprise service URLs
attempts:
retries:
base_delay_in_seconds: 14.0625 # base_delay_in_seconds * 2^max = 14.0625 * 2^6 = 900 = 15 minutes
max: 8
Expand Down
7 changes: 4 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import random
import re
import sys
import traceback
from datetime import datetime
from enum import Enum, auto

Expand Down Expand Up @@ -41,7 +42,7 @@ def main():
logging.error("", exc_info=True)
Utils.sendNotification(
f"⚠️ Error executing {currentAccount.username}, please check the log",
f"{e1}\n{e1.__traceback__}",
traceback.format_exc(),
)
continue
previous_points = previous_points_data.get(currentAccount.username, 0)
Expand Down Expand Up @@ -238,7 +239,7 @@ def executeBot(currentAccount: Account, args: argparse.Namespace):
logging.info(
f"[POINTS] You have {utils.formatNumber(startingPoints)} points on your account"
)
# todo Send notification if these fail to Apprise versus just logging
# todo Combine these classes so main loop isn't duplicated
DailySet(desktopBrowser).completeDailySet()
PunchCards(desktopBrowser).completePunchCards()
MorePromotions(desktopBrowser).completeMorePromotions()
Expand Down Expand Up @@ -356,5 +357,5 @@ def save_previous_points_data(data):
except Exception as e:
logging.exception("")
Utils.sendNotification(
"⚠️ Error occurred, please check the log", f"{e}\n{e.__traceback__}"
"⚠️ Error occurred, please check the log", traceback.format_exc()
)
7 changes: 4 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
requests
requests~=2.32.3
selenium>=4.15.2 # not directly required, pinned by Snyk to avoid a vulnerability
ipapi~=1.0.4
undetected-chromedriver==3.5.5
selenium-wire
selenium-wire~=5.1.0
numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability
setuptools
psutil
blinker==1.7.0 # prevents issues on newer versions
apprise~=1.8.1
pyyaml~=6.0.2
urllib3>=2.2.2 # not directly required, pinned by Snyk to avoid a vulnerability
requests-oauthlib
requests-oauthlib~=2.0.0
zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability
pyotp~=2.9.0
1 change: 1 addition & 0 deletions src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .account import Account
from .remainingSearches import RemainingSearches
from .browser import Browser
from .dailySet import DailySet
from .login import Login
Expand Down
1 change: 1 addition & 0 deletions src/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
class Account:
username: str
password: str
totp: str | None = None
proxy: str | None = None
63 changes: 32 additions & 31 deletions src/activities.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
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

Expand Down Expand Up @@ -39,19 +42,22 @@ def completeSurvey(self):

def completeQuiz(self):
# Simulate completing a quiz activity
startQuiz = self.browser.utils.waitUntilQuizLoads()
startQuiz.click()
with contextlib.suppress(TimeoutException):
startQuiz = self.browser.utils.waitUntilQuizLoads()
self.browser.utils.click(startQuiz)
self.browser.utils.waitUntilVisible(
By.XPATH, '//*[@id="currentQuestionContainer"]/div/div[1]', 5
By.ID, "overlayPanel", 5
)
time.sleep(random.randint(10, 15))
numberOfQuestions = self.webdriver.execute_script(
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 question in range(numberOfQuestions):
for _ in range(currentQuestionNumber, maxQuestions + 1):
if numberOfOptions == 8:
answers = []
for i in range(numberOfOptions):
Expand All @@ -61,8 +67,8 @@ def completeQuiz(self):
if isCorrectOption and isCorrectOption.lower() == "true":
answers.append(f"rqAnswerOption{i}")
for answer in answers:
self.webdriver.find_element(By.ID, answer).click()
time.sleep(random.randint(10, 15))
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(
Expand All @@ -75,14 +81,11 @@ def completeQuiz(self):
).get_attribute("data-option")
== correctOption
):
self.webdriver.find_element(By.ID, f"rqAnswerOption{i}").click()
time.sleep(random.randint(10, 15))
element = self.webdriver.find_element(By.ID, f"rqAnswerOption{i}")
self.browser.utils.click(element)

self.browser.utils.waitUntilQuestionRefresh()
break
if question + 1 != numberOfQuestions:
time.sleep(random.randint(10, 15))
time.sleep(random.randint(10, 15))
self.browser.utils.closeCurrentTab()

def completeABC(self):
Expand All @@ -92,19 +95,19 @@ def completeABC(self):
).text[:-1][1:]
numberOfQuestions = max(int(s) for s in counter.split() if s.isdigit())
for question in range(numberOfQuestions):
self.webdriver.find_element(
By.ID, f"questionOptionChoice{question}{random.randint(0, 2)}"
).click()
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))
self.webdriver.find_element(By.ID, f"nextQuestionbtn{question}").click()
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()
startQuiz.click()
self.browser.utils.click(startQuiz)
self.browser.utils.waitUntilVisible(
By.XPATH, '//*[@id="currentQuestionContainer"]/div/div[1]', 10
)
Expand All @@ -115,26 +118,24 @@ def completeThisOrThat(self):
)
answer1, answer1Code = self.getAnswerAndCode("rqAnswerOption0")
answer2, answer2Code = self.getAnswerAndCode("rqAnswerOption1")
answerToClick: WebElement
if answer1Code == correctAnswerCode:
answer1.click()
time.sleep(random.randint(10, 15))
answerToClick = answer1
elif answer2Code == correctAnswerCode:
answer2.click()
time.sleep(random.randint(10, 15))
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:
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")
if answerTitle is not None:
return (
answer,
self.browser.utils.getAnswerCode(answerEncodeKey, answerTitle),
)
else:
# todo - throw exception?
return answer, None
return (
answer,
self.browser.utils.getAnswerCode(answerEncodeKey, answerTitle),
)
Loading

0 comments on commit 54e00cb

Please sign in to comment.