Compare commits
87 Commits
Author | SHA1 | Date | |
---|---|---|---|
5e5f7f7407 | |||
0cc42823ff | |||
56c858fa90 | |||
adf91ac8f0 | |||
2721c76686 | |||
|
45ea72a354 | ||
|
18b35f6f68 | ||
c53125479e | |||
df256f2405 | |||
31b4d5d7da | |||
574fa45813 | |||
|
3bb49d1618 | ||
|
e25334f1bd | ||
|
41312b53b9 | ||
|
1ac98f14c4 | ||
|
a197d44768 | ||
|
f9a21f5b98 | ||
|
82161973a9 | ||
|
c7e110787b | ||
1db1de9606 | |||
10d75f9d78 | |||
87fb791436 | |||
6995bde8a6 | |||
92069a013e | |||
906d3e7822 | |||
778adc67d2 | |||
402b8cd3ef | |||
|
3c74aa025e | ||
|
3123a1dbd0 | ||
|
27d45e88dd | ||
|
0685a42922 | ||
|
6729703827 | ||
|
304a222de1 | ||
|
04d33f4ecd | ||
|
ac22814605 | ||
|
b7a89b56d0 | ||
|
127d16afea | ||
|
9bcbc81c2f | ||
|
12eba5cde7 | ||
|
22ea727c47 | ||
|
05a1fd8557 | ||
|
39e226b564 | ||
|
cd00c8ca88 | ||
|
43793d2c2c | ||
|
ee6ec458fc | ||
|
e6e8bdaa06 | ||
9bd96ad876 | |||
ed517e7b03 | |||
01416c0e11 | |||
19cf77c6bd | |||
feb7834d1f | |||
1ea1ff776e | |||
ae023688a4 | |||
fcb40537dc | |||
d7d2f49a3f | |||
68b5de32d6 | |||
c5e9fb1267 | |||
6ade73617a | |||
37e8f6f61b | |||
db6fa9b6b0 | |||
d6988c03b4 | |||
449d2da410 | |||
3eb193eca3 | |||
f566b2eeda | |||
52e88f81b9 | |||
1a8137783c | |||
6f13b2532d | |||
3978c44bbc | |||
49dc53ed32 | |||
ba66a96c65 | |||
db157771de | |||
cbd1ad93a6 | |||
afabd94f0d | |||
81deaf05b0 | |||
9af0f4aadb | |||
1d16294c04 | |||
fae2033061 | |||
50c4036c73 | |||
c683472895 | |||
178f2d472a | |||
d3137f858a | |||
|
d2ad467d4e | ||
|
b45e9e549f | ||
|
200b0d8a86 | ||
|
4a5af6455d | ||
|
49b691d736 | ||
|
9549a6dea3 |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@ -0,0 +1,2 @@
|
||||
**/.venv
|
||||
user_data/*
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -3,18 +3,17 @@ geckodriver.log
|
||||
.vscode/
|
||||
.idea
|
||||
venv
|
||||
**/.venv
|
||||
/Git
|
||||
page.html
|
||||
screenshot.png
|
||||
login.csv
|
||||
data
|
||||
**/__pycache__
|
||||
user_data/*
|
||||
install.sh
|
||||
nohup.out
|
||||
points.csv
|
||||
file.png
|
||||
user_data/configs.json
|
||||
*.ts
|
||||
LICENSE
|
||||
README.md
|
||||
venv
|
||||
|
55
Dockerfile
55
Dockerfile
@ -1,22 +1,43 @@
|
||||
FROM python:3.10
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
WORKDIR /app/
|
||||
|
||||
# Initial apt install
|
||||
RUN apt update
|
||||
RUN apt install -y libgtk-4-1 libvulkan1 libxdamage1 \
|
||||
novnc websockify xvfb nginx nano tzdata \
|
||||
sqlite3 apt-transport-https software-properties-common \
|
||||
wget wfrench tigervnc-standalone-server libasound2 \
|
||||
libatk-bridge2.0-0 libnss3 libnspr4 xvfb libgbm1 libatk1.0-0 \
|
||||
libu2f-udev libatspi2.0-0 libcups2 libxkbcommon0 libxrandr2 \
|
||||
libdbus-1-3 xdg-utils fonts-liberation libdrm2
|
||||
|
||||
# Additional repos and packages
|
||||
RUN wget http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.0g-2ubuntu4_amd64.deb \
|
||||
&& dpkg -i libssl1.1_1.1.0g-2ubuntu4_amd64.deb
|
||||
RUN curl -sSL http://mirror.cs.uchicago.edu/google-chrome/pool/main/g/google-chrome-stable/google-chrome-stable_123.0.6312.86-1_amd64.deb -o chrome.deb \
|
||||
&& dpkg -i chrome.deb
|
||||
RUN ln -fs /usr/share/zoneinfo/Europe/Paris /etc/localtime
|
||||
RUN wget -q -O /usr/share/keyrings/grafana.key https://apt.grafana.com/gpg.key \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://apt.grafana.com stable main" | tee -a /etc/apt/sources.list.d/grafana.list
|
||||
RUN curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
|
||||
# Install from new repo
|
||||
RUN apt update \
|
||||
&& wget http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.0g-2ubuntu4_amd64.deb \
|
||||
&& dpkg -i libssl1.1_1.1.0g-2ubuntu4_amd64.deb \
|
||||
&& apt install redis libgtk-4-1 libvulkan1 libxdamage1 -y \
|
||||
&& curl -sSLO https://nc.piair.xyz/s/BKLsBWoZkTdYjfq/download/chrome.deb \
|
||||
&& ln -fs /usr/share/zoneinfo/Europe/Paris /etc/localtime \
|
||||
&& git clone https://gitea.augustin64.fr/piair/MsRewards-Reborn \
|
||||
&& python3 -m pip install -r MsRewards-Reborn/requirements.txt \
|
||||
&& wget -q -O /usr/share/keyrings/grafana.key https://apt.grafana.com/gpg.key \
|
||||
&& curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/grafana.key] https://apt.grafana.com stable main" | tee -a /etc/apt/sources.list.d/grafana.list \
|
||||
&& apt update \
|
||||
&& apt install novnc websockify grafana xvfb nginx nano tzdata sqlite3 apt-transport-https software-properties-common wget wfrench tigervnc-standalone-server libasound2 libatk-bridge2.0-0 libnss3 libnspr4 xvfb libgbm1 libatk1.0-0 libu2f-udev libatspi2.0-0 libcups2 libxkbcommon0 libxrandr2 libdbus-1-3 xdg-utils fonts-liberation libdrm2 -y \
|
||||
&& bash MsRewards-Reborn/config/config.sh \
|
||||
&& dpkg -i chrome.deb
|
||||
|
||||
ENV TZ="Europe/Paris"
|
||||
&& apt install -y redis grafana
|
||||
|
||||
# Configure Grafana
|
||||
RUN grafana-cli plugins install frser-sqlite-datasource
|
||||
|
||||
COPY requirements.txt /app/requirements.txt
|
||||
RUN python3 -m pip install -r requirements.txt
|
||||
|
||||
# Setup app
|
||||
RUN git clone https://gitea.augustin64.fr/piair/MsRewards-Reborn
|
||||
# Use this instead when developping locally:
|
||||
# COPY . /app/MsRewards-Reborn
|
||||
|
||||
RUN bash MsRewards-Reborn/config/config.sh
|
||||
|
||||
ENV TZ="Europe/Paris"
|
||||
WORKDIR /app/MsRewards-Reborn/Flask/
|
||||
CMD bash start.sh
|
||||
|
101
Flask/app.py
101
Flask/app.py
@ -13,11 +13,27 @@ import re
|
||||
from requests import get
|
||||
import redis
|
||||
|
||||
APP_ROOT = os.getenv("APP_ROOT")
|
||||
if APP_ROOT is None:
|
||||
APP_ROOT = "/app/MsRewards-Reborn/"
|
||||
|
||||
NO_SUBPROCESS = os.getenv("NO_SUBPROCESS")
|
||||
if NO_SUBPROCESS is not None:
|
||||
def fake_popen(*args, **kwargs):
|
||||
print("Calling subprocess.Popen with", args, kwargs)
|
||||
|
||||
subprocess.Popen = fake_popen
|
||||
print("Faking subprocess calls")
|
||||
|
||||
|
||||
|
||||
# redis part for live update
|
||||
pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
|
||||
r = redis.Redis(connection_pool=pool)
|
||||
|
||||
def get_path(path):
|
||||
return os.path.join(APP_ROOT, path)
|
||||
|
||||
def generate_output():
|
||||
pubsub = r.pubsub()
|
||||
pubsub.subscribe('console')
|
||||
@ -32,7 +48,7 @@ def generate_output():
|
||||
# the end
|
||||
|
||||
global password
|
||||
with open("/app/MsRewards-Reborn/user_data/flask.json", "r") as inFile:
|
||||
with open(get_path("user_data/flask.json"), "r") as inFile:
|
||||
data = json.load(inFile)
|
||||
|
||||
password = data["password"]
|
||||
@ -40,7 +56,7 @@ secret = data["secret"]
|
||||
if secret == "":
|
||||
import secrets
|
||||
secret = secrets.token_hex()
|
||||
with open("/app/MsRewards-Reborn/user_data/flask.json", "w") as inFile:
|
||||
with open(get_path("user_data/flask.json"), "w") as inFile:
|
||||
data = {
|
||||
"password": password,
|
||||
"secret": secret
|
||||
@ -70,14 +86,14 @@ scheduler.add_job( # on relance le job
|
||||
|
||||
def start_ms(i):
|
||||
print("\033[32m" + f"Starting config {i}" + "\033[0m")
|
||||
log = open(f"/app/MsRewards-Reborn/Flask/static/logs/{i}.txt", 'a') # so that data written to it will be appended
|
||||
subprocess.Popen([f"python3 -u /app/MsRewards-Reborn/V6.py -c {i}"], stdout=log, stderr=log, shell=True)
|
||||
log = open(get_path(f"Flask/static/logs/{i}.txt"), 'a') # so that data written to it will be appended
|
||||
subprocess.Popen([f"python3 -u {get_path('V6.py')} -c {i}"], stdout=log, stderr=log, shell=True)
|
||||
log.close()
|
||||
|
||||
|
||||
TriggerDict = {}
|
||||
def update_jobs():
|
||||
with open("/app/MsRewards-Reborn/user_data/configs.json", "r") as inFile:
|
||||
with open(get_path("user_data/configs.json"), "r") as inFile:
|
||||
configs = json.load(inFile)
|
||||
for i in configs:
|
||||
try :
|
||||
@ -120,7 +136,7 @@ app = Flask(__name__)
|
||||
|
||||
@app.context_processor
|
||||
def inject_default_variables():
|
||||
with open("/app/MsRewards-Reborn/version", "r") as f:
|
||||
with open(get_path("version"), "r") as f:
|
||||
version = f.readline().replace("\n", '')
|
||||
return dict(version=version)
|
||||
"""
|
||||
@ -175,7 +191,7 @@ def change_password():
|
||||
if request.method == 'POST':
|
||||
password = request.form["password"]
|
||||
subprocess.Popen(["grafana-cli", "admin", "reset-admin-password", password])
|
||||
with open("/app/MsRewards-Reborn/user_data/flask.json", "w") as inFile:
|
||||
with open(get_path("user_data/flask.json"), "w") as inFile:
|
||||
data = {
|
||||
"password": password,
|
||||
"secret": secret
|
||||
@ -201,21 +217,21 @@ def load_user(userid):
|
||||
|
||||
@app.route("/")
|
||||
def main():
|
||||
with open("/app/MsRewards-Reborn/user_data/configs.json", "r") as inFile:
|
||||
with open(get_path("user_data/configs.json"), "r") as inFile:
|
||||
configs = json.load(inFile)
|
||||
return(render_template("schedule.html", data=configs))
|
||||
|
||||
|
||||
@app.route("/discord/")
|
||||
def discord_get():
|
||||
with open("/app/MsRewards-Reborn/user_data/discord.json", "r") as inFile:
|
||||
with open(get_path("user_data/discord.json"), "r") as inFile:
|
||||
data = json.load(inFile)
|
||||
return(render_template("discord.html", data=data, len=maxi(data)))
|
||||
|
||||
|
||||
@app.route("/discord/", methods=["post"])
|
||||
def discord_post():
|
||||
with open("/app/MsRewards-Reborn/user_data/discord.json", "r") as inFile:
|
||||
with open(get_path("user_data/discord.json"), "r") as inFile:
|
||||
data = json.load(inFile)
|
||||
action = request.form
|
||||
if action['DISCORD'] == "delete" :
|
||||
@ -237,7 +253,7 @@ def discord_post():
|
||||
name = action["name"] if action["name"] else f"unnamed{action['select']}"
|
||||
data[config] = {"errorsL" : errorsL, "errorsT": errorsT, "successT": successT, "successL": successL, "name": name}
|
||||
|
||||
with open("/app/MsRewards-Reborn/user_data/discord.json", "w") as outFile:
|
||||
with open(get_path("user_data/discord.json"), "w") as outFile:
|
||||
json.dump(data, outFile)
|
||||
return(render_template("discord.html", data=data, len=maxi(data)))
|
||||
|
||||
@ -249,7 +265,7 @@ def dev2():
|
||||
|
||||
@app.route("/settings/")
|
||||
def settings_get():
|
||||
with open("/app/MsRewards-Reborn/user_data/settings.json", "r") as inFile:
|
||||
with open(get_path("user_data/settings.json"), "r") as inFile:
|
||||
settings = json.load(inFile)
|
||||
return(render_template("settings.html", data=settings))
|
||||
|
||||
@ -259,21 +275,21 @@ def settings_post():
|
||||
settings = {}
|
||||
action = request.form
|
||||
settings['avatarlink'] = action["avatarlink"]
|
||||
with open("/app/MsRewards-Reborn/user_data/settings.json", "w") as inFile:
|
||||
with open(get_path("user_data/settings.json"), "w") as inFile:
|
||||
json.dump(settings, inFile)
|
||||
return(render_template("settings.html", data=settings))
|
||||
|
||||
|
||||
@app.route("/proxy/")
|
||||
def proxy_get():
|
||||
with open("/app/MsRewards-Reborn/user_data/proxy.json", "r") as inFile:
|
||||
with open(get_path("user_data/proxy.json"), "r") as inFile:
|
||||
j = json.load(inFile)
|
||||
return(render_template("proxy.html", data=j, len=maxi(j)))
|
||||
|
||||
|
||||
@app.route("/proxy/", methods=["post"])
|
||||
def proxy_post():
|
||||
with open("/app/MsRewards-Reborn/user_data/proxy.json", "r") as inFile:
|
||||
with open(get_path("user_data/proxy.json"), "r") as inFile:
|
||||
data = json.load(inFile)
|
||||
action = request.form
|
||||
print(action)
|
||||
@ -290,21 +306,21 @@ def proxy_post():
|
||||
except :
|
||||
print("error : probably bad config")
|
||||
|
||||
with open("/app/MsRewards-Reborn/user_data/proxy.json", "w") as outFile:
|
||||
with open(get_path("user_data/proxy.json"), "w") as outFile:
|
||||
json.dump(data, outFile)
|
||||
return(render_template("proxy.html", data=data, len=maxi(data)))
|
||||
|
||||
|
||||
@app.route("/schedule/")
|
||||
def schedule_get():
|
||||
with open("/app/MsRewards-Reborn/user_data/configs.json", "r") as inFile:
|
||||
with open(get_path("user_data/configs.json"), "r") as inFile:
|
||||
configs = json.load(inFile)
|
||||
return(render_template("schedule.html", data=configs))
|
||||
|
||||
|
||||
@app.route("/schedule/", methods=["post"])
|
||||
def schedule_post():
|
||||
with open("/app/MsRewards-Reborn/user_data/configs.json", "r") as inFile:
|
||||
with open(get_path("user_data/configs.json"), "r") as inFile:
|
||||
configs = json.load(inFile)
|
||||
|
||||
data = dict(request.form)
|
||||
@ -318,7 +334,7 @@ def schedule_post():
|
||||
configs[i]["time"] = data[f"time{i}"]
|
||||
configs[i]["enabled"] = data[f"switch{i}"] == "on"
|
||||
|
||||
with open("/app/MsRewards-Reborn/user_data/configs.json", "w") as inFile:
|
||||
with open(get_path("user_data/configs.json"), "w") as inFile:
|
||||
json.dump(configs, inFile)
|
||||
update_jobs()
|
||||
return(render_template("schedule.html", data=configs))
|
||||
@ -326,11 +342,11 @@ def schedule_post():
|
||||
|
||||
@app.route("/config/")
|
||||
def config_get():
|
||||
with open("/app/MsRewards-Reborn/user_data/proxy.json", "r") as inFile:
|
||||
with open(get_path("user_data/proxy.json"), "r") as inFile:
|
||||
proxys = json.load(inFile)
|
||||
with open("/app/MsRewards-Reborn/user_data/discord.json", "r") as inFile:
|
||||
with open(get_path("user_data/discord.json"), "r") as inFile:
|
||||
discords = json.load(inFile)
|
||||
with open("/app/MsRewards-Reborn/user_data/configs.json", "r") as inFile:
|
||||
with open(get_path("user_data/configs.json"), "r") as inFile:
|
||||
configs = json.load(inFile)
|
||||
return(render_template("config.html", data=configs, discords=discords, proxys=proxys, configs=configs, len=maxi(configs)))
|
||||
|
||||
@ -338,11 +354,11 @@ def config_get():
|
||||
@app.route("/config/", methods=["POST"])
|
||||
def config_post():
|
||||
action = request.form
|
||||
with open("/app/MsRewards-Reborn/user_data/proxy.json", "r") as inFile:
|
||||
with open(get_path("user_data/proxy.json"), "r") as inFile:
|
||||
proxys = json.load(inFile)
|
||||
with open("/app/MsRewards-Reborn/user_data/discord.json", "r") as inFile:
|
||||
with open(get_path("user_data/discord.json"), "r") as inFile:
|
||||
discords = json.load(inFile)
|
||||
with open("/app/MsRewards-Reborn/user_data/configs.json", "r") as inFile:
|
||||
with open(get_path("user_data/configs.json"), "r") as inFile:
|
||||
configs = json.load(inFile)
|
||||
|
||||
if action["data"] == "delete":
|
||||
@ -365,16 +381,26 @@ def config_post():
|
||||
"enabled":"False",
|
||||
"accounts": comptes
|
||||
}
|
||||
with open("/app/MsRewards-Reborn/user_data/configs.json", "w") as outFile:
|
||||
with open(get_path("user_data/configs.json"), "w") as outFile:
|
||||
json.dump(configs, outFile)
|
||||
return(render_template("config.html", data=configs, discords=discords, proxys=proxys, configs=configs, len=maxi(configs)))
|
||||
|
||||
@app.route("/logs/", methods=["GET", "POST"])
|
||||
def logs():
|
||||
with open("/app/MsRewards-Reborn/user_data/configs.json", "r") as inFile:
|
||||
with open(get_path("user_data/configs.json"), "r") as inFile:
|
||||
configs = json.load(inFile)
|
||||
print(configs)
|
||||
return(render_template("logs.html", data=configs))
|
||||
|
||||
files = [(configs[i]["name"], i) for i in configs]
|
||||
config_files = [i[1] for i in files]
|
||||
for f in os.listdir(get_path("Flask/static/logs")):
|
||||
fid = ".".join(f.split(".")[:-1]) # filename without .txt
|
||||
if f != ".gitignore" and fid not in config_files:
|
||||
files.append((f, fid))
|
||||
|
||||
return render_template(
|
||||
"logs.html",
|
||||
files=files
|
||||
)
|
||||
|
||||
|
||||
@app.route("/stats/", methods=["GET", "POST"])
|
||||
@ -385,21 +411,21 @@ def stats():
|
||||
@app.route("/override/", methods=["POST"])
|
||||
def override_post():
|
||||
json = request.form.to_dict(flat=False)
|
||||
log = open(f"/app/MsRewards-Reborn/Flask/static/logs/custom.txt", 'w') # so that data written to it will be appended
|
||||
subprocess.Popen([f"python3 -u /app/MsRewards-Reborn/V6.py -c {json['config'][0]} --json \"{json}\""], stdout=log, stderr=log, shell=True)
|
||||
log = open(get_path("Flask/static/logs/custom.txt"), 'w') # so that data written to it will be appended
|
||||
subprocess.Popen([f"python3 -u {get_path('V6.py')} -c {json['config'][0]} --json \"{json}\""], stdout=log, stderr=log, shell=True)
|
||||
log.close()
|
||||
return(render_template("vnc_post.html"))
|
||||
|
||||
@app.route("/override/", methods=["GET"])
|
||||
def override_get():
|
||||
with open("/app/MsRewards-Reborn/user_data/configs.json", "r") as inFile:
|
||||
with open(get_path("user_data/configs.json"), "r") as inFile:
|
||||
configs = json.load(inFile)
|
||||
return(render_template("vnc_get.html", configs=configs))
|
||||
|
||||
@app.route('/download/<path:filename>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def download(filename):
|
||||
return send_from_directory(directory='/app/MsRewards-Reborn/user_data/', path=filename, as_attachment=True)
|
||||
return send_from_directory(directory=get_path("user_data/"), path=filename, as_attachment=True)
|
||||
|
||||
|
||||
def allowed_file(filename):
|
||||
@ -420,8 +446,8 @@ def upload_file():
|
||||
|
||||
elif file and allowed_file(file.filename):
|
||||
filename = secure_filename(file.filename)
|
||||
print(os.path.join('/app/MsRewards-Reborn/user_data/', filename))
|
||||
file.save(os.path.join('/app/MsRewards-Reborn/user_data/', filename))
|
||||
print(os.path.join(get_path("user_data/"), filename))
|
||||
file.save(os.path.join(get_path("user_data/"), filename))
|
||||
|
||||
i += 1
|
||||
print(i)
|
||||
@ -439,4 +465,7 @@ def maxi(dict):
|
||||
|
||||
|
||||
update_jobs()
|
||||
subprocess.Popen(["bash",'/app/MsRewards-Reborn/config/request.sh'])
|
||||
subprocess.Popen(["bash", get_path("config/request.sh")])
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
|
17
Flask/static/css/vnc-post.css
Normal file
17
Flask/static/css/vnc-post.css
Normal file
@ -0,0 +1,17 @@
|
||||
#console {
|
||||
height: 100%;
|
||||
width: 20%;
|
||||
float: left;
|
||||
resize: horizontal;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#vnc-container {
|
||||
height: 100%;
|
||||
width: 80%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
}
|
@ -8,14 +8,37 @@
|
||||
|
||||
<select name="select" onchange="change_logs(this.value)">
|
||||
<option id="null" value="0">Choisir une config</option>
|
||||
{% for i in data %}
|
||||
<option id="{{data[i]['name']}}" value="{{i}}">{{data[i]['name']}}</option>
|
||||
{% for file in files %}
|
||||
<option id="{{ file[0] }}" value="{{ file[1] }}">{{ file[0] }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<br><br>
|
||||
<iframe type="text/html" src="{{url_for('static', filename='logs/1.txt')}}" width="100%" height="85%" id="embed">
|
||||
<iframe type="text/html" src="{{url_for('static', filename='logs/1.txt')}}" width="100%" height="85%" id="embed"></iframe>
|
||||
|
||||
<script defer>
|
||||
const iframe = document.getElementsByTagName("iframe")[0];
|
||||
var script = document.createElement('script');
|
||||
|
||||
// Wait until ansi_up load
|
||||
script.onload = function () {
|
||||
// Wait until iframe load
|
||||
iframe.onload = function() {
|
||||
const subdoc = iframe.contentWindow.document;
|
||||
const subBody = subdoc.getElementsByTagName("body")[0]
|
||||
let ansiOutput = subBody;
|
||||
// Depending on the content encoding (and maybe on the browser)
|
||||
// a <pre> is added around the content of the file
|
||||
if (subBody.getElementsByTagName("pre").length > 0) {
|
||||
ansiOutput = subBody.getElementsByTagName("pre")[0];
|
||||
}
|
||||
const ansi_up = new AnsiUp();
|
||||
ansiOutput.innerHTML = ansi_up.ansi_to_html(ansiOutput.innerText);
|
||||
}
|
||||
};
|
||||
|
||||
script.src = "https://cdn.jsdelivr.net/npm/ansi_up@4.0.4/ansi_up.js";
|
||||
document.head.appendChild(script);
|
||||
</script>
|
||||
|
||||
{% endif %}
|
||||
{% endblock %}
|
@ -6,19 +6,19 @@
|
||||
{% if not current_user.is_authenticated %}
|
||||
<button class="unselected" onclick="location.href = '/login';">login</button>
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/vnc-post.css') }}"/>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='node_modules/xterm/css/xterm.css') }}"/>
|
||||
<script src="{{ url_for('static', filename='node_modules/xterm/lib/xterm.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='node_modules/xterm-addon-fit/lib/xterm-addon-fit.js') }}"></script>
|
||||
<script>
|
||||
document.getElementsByClassName("content")[0].style.padding = "0 0"
|
||||
</script>
|
||||
<div style="height: 100%">
|
||||
|
||||
<div id="console" style="height: 100%; width: 20%; float:left;"></div>
|
||||
|
||||
<div style="height: 100%; width: 80%; float:left;">
|
||||
<div class="container">
|
||||
<div id="console"></div>
|
||||
<div id="vnc-container">
|
||||
<iframe src="/novnc/vnc.html?resize=scale&path=novnc/websockify&autoconnect=true&view_only"
|
||||
width="100%" height="100%" frameborder="0"></iframe>
|
||||
width="100%" height="100%" frameborder="0">
|
||||
</iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
475
V6.py
475
V6.py
@ -1,8 +1,11 @@
|
||||
#!/usr/bin/python3.10
|
||||
#!/usr/bin/python3
|
||||
import random
|
||||
import subprocess
|
||||
|
||||
from modules.Classes.Config import Config
|
||||
from modules.Classes.DiscordLogger import DiscordLogger
|
||||
from modules.Classes.UserCredentials import UserCredentials
|
||||
from modules.Tools.logger import warning, critical
|
||||
from modules.Tools.logger import critical, warning
|
||||
from modules.cards import *
|
||||
from modules.config import *
|
||||
from modules.db import add_to_database
|
||||
@ -22,16 +25,33 @@ def create_driver(mobile=False):
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko)"
|
||||
"Chrome/22 Mobile Safari/537.36"
|
||||
)
|
||||
chrome_options = webdriver.ChromeOptions()
|
||||
|
||||
chrome_profile_dir = init_profile(config.UserCredentials.get_mail(), mobile=mobile)
|
||||
|
||||
# Full list on https://github.com/GoogleChrome/chrome-launcher/blob/main/docs/chrome-flags-for-tools.md
|
||||
arguments = [
|
||||
"--no-first-run",
|
||||
"--ash-no-nudges",
|
||||
"--no-default-browser-check",
|
||||
"--disable-features=PrivacySandboxSettings4,Translate",
|
||||
"--disable-search-engine-choice-screen",
|
||||
f"--user-data-dir={chrome_profile_dir}/"
|
||||
]
|
||||
|
||||
if mobile:
|
||||
chrome_options.add_argument(f"--user-agent={mobile_user_agent}")
|
||||
arguments.append(f"--user-agent={mobile_user_agent}")
|
||||
else:
|
||||
chrome_options.add_argument(f"--user-agent={pc_user_agent}")
|
||||
arguments.append(f"--user-agent={pc_user_agent}")
|
||||
|
||||
# disabled as it may cause detection
|
||||
if config.proxy.is_enabled():
|
||||
chrome_options.add_argument(f'--proxy-server={config.proxy.ip}:{config.proxy.port}')
|
||||
arguments.append(f'--proxy-server={config.proxy.ip}:{config.proxy.port}')
|
||||
|
||||
chrome_options = webdriver.ChromeOptions()
|
||||
for arg in arguments:
|
||||
chrome_options.add_argument(arg)
|
||||
|
||||
driver = uc.Chrome(options=chrome_options)
|
||||
set_language(driver)
|
||||
return driver
|
||||
|
||||
|
||||
@ -152,7 +172,7 @@ def play_quiz4(override: int = None):
|
||||
except Exception as e:
|
||||
log_error(e)
|
||||
raise ValueError(e)
|
||||
info("Quiz 8 done.")
|
||||
info("Quiz 4 done.")
|
||||
custom_sleep(3)
|
||||
|
||||
|
||||
@ -165,6 +185,8 @@ def do_poll():
|
||||
try:
|
||||
answer_elem.click()
|
||||
except exceptions.ElementNotInteractableException:
|
||||
warning("element not clickable. Waiting a bit and retrying.")
|
||||
custom_sleep(uniform(2, 2.5))
|
||||
driver.execute_script("arguments[0].click();", answer_elem)
|
||||
custom_sleep(uniform(2, 2.5))
|
||||
except Exception as err:
|
||||
@ -177,6 +199,68 @@ def do_poll():
|
||||
# Find each playable card and tries to click on it to earn points
|
||||
def all_cards():
|
||||
driver = config.WebDriver.driver
|
||||
|
||||
def check_popups() -> bool:
|
||||
"""
|
||||
Check various annoying popups
|
||||
"""
|
||||
def check_welcome_tour() -> bool:
|
||||
if "rewards.bing.com/welcometour" not in driver.current_url:
|
||||
return False
|
||||
|
||||
info("Popup 'Explorer le programme' reçue")
|
||||
wait_until_visible(By.ID, "welcome-tour", timeout=5, browser=driver)
|
||||
custom_sleep(1.5)
|
||||
|
||||
welcome_tour = driver.find_element(By.ID, "welcome-tour")
|
||||
interest_button_box = welcome_tour.find_element(By.CLASS_NAME, "interest-buttons")
|
||||
interests = interest_button_box.find_elements(By.CLASS_NAME, "ng-binding")
|
||||
debug("Got the following interests: " + str(interests))
|
||||
random.choice(interests).click() # Choose interest
|
||||
custom_sleep(1.5)
|
||||
|
||||
claim_button = welcome_tour.find_element(By.ID, "claim-button")
|
||||
claim_button.click() # submit
|
||||
custom_sleep(1.5)
|
||||
return True
|
||||
|
||||
def check_streak_protection() -> bool:
|
||||
"""
|
||||
Ne perdez plus jamais votre série !
|
||||
"""
|
||||
try:
|
||||
streak_protection_close = driver.find_element(By.ID, "streak-protection-popup-close-cross")
|
||||
streak_protection_activate = driver.find_elements(By.CLASS_NAME, "earningPagePopUpPopUpSelectButton")
|
||||
streak_protection_activate[0].click()
|
||||
info("Popup 'Streak Protection' reçue")
|
||||
custom_sleep(1.5)
|
||||
|
||||
return True
|
||||
except (exceptions.NoSuchElementException, exceptions.ElementNotInteractableException, IndexError):
|
||||
# les éléments sont présents dans le DOM même quand la popup n'est pas visible apparemment
|
||||
return False
|
||||
|
||||
def check_streak_protection_expired() -> bool:
|
||||
"""
|
||||
Non, vous avez utilisé tous vos jours de protection contre les séries !
|
||||
"""
|
||||
try:
|
||||
streak_protection_close = driver.find_element(By.ID, "streak-protection-popup-close-cross")
|
||||
ok_sad_button = driver.find_elements(By.CLASS_NAME, "dashboardPopUpPopUpSelectButton")
|
||||
ok_sad_button[0].click()
|
||||
info("Popup 'Streak Protection fully used' reçue")
|
||||
custom_sleep(1.5)
|
||||
|
||||
return True
|
||||
except (exceptions.NoSuchElementException, exceptions.ElementNotInteractableException, IndexError):
|
||||
return False
|
||||
|
||||
return (
|
||||
check_welcome_tour()
|
||||
or check_streak_protection()
|
||||
or check_streak_protection_expired()
|
||||
)
|
||||
|
||||
driver.get("https://rewards.bing.com")
|
||||
wait_until_visible(By.CLASS_NAME, "c-card-content", 10, driver)
|
||||
|
||||
@ -190,7 +274,7 @@ def all_cards():
|
||||
info("no promo card")
|
||||
|
||||
if len(card_list) < 10: # most likely an error during loading
|
||||
if "suspendu" in driver.page_source:
|
||||
if "suspendu" in driver.page_source or "suspended" in driver.page_source:
|
||||
raise Banned()
|
||||
driver.refresh()
|
||||
card_list = driver.find_elements(By.CLASS_NAME, "c-card-content")
|
||||
@ -220,39 +304,53 @@ def all_cards():
|
||||
if i == len(card_list) and i > 15:
|
||||
checked = False
|
||||
|
||||
if checked:
|
||||
custom_sleep(1.5)
|
||||
if not checked:
|
||||
continue
|
||||
|
||||
try:
|
||||
activity = findall("data-bi-id=\"([^\"]+)\"", card_list[i].get_attribute("innerHTML"))[0]
|
||||
except Exception as e:
|
||||
warning("Can't find activity." + str(e))
|
||||
activity = ""
|
||||
|
||||
custom_sleep(1.5)
|
||||
check_popups()
|
||||
driver.execute_script("arguments[0].scrollIntoView();", card_list[i])
|
||||
custom_sleep(1.5)
|
||||
card_list[i].click()
|
||||
|
||||
if len(driver.window_handles) > 1:
|
||||
driver.switch_to.window(driver.window_handles[1])
|
||||
try_play(driver.title, activity)
|
||||
close_tab(driver.window_handles[1])
|
||||
|
||||
try:
|
||||
driver.refresh()
|
||||
card_list = driver.find_elements(By.CLASS_NAME, "c-card-content")
|
||||
|
||||
if "mee-icon-AddMedium" not in card_list[i].get_attribute("innerHTML"):
|
||||
continue
|
||||
|
||||
check_popups()
|
||||
driver.execute_script("arguments[0].scrollIntoView();", card_list[i])
|
||||
custom_sleep(1.5)
|
||||
card_list[i].click()
|
||||
|
||||
if len(driver.window_handles) > 1:
|
||||
driver.switch_to.window(driver.window_handles[1])
|
||||
try_play(driver.title)
|
||||
close_tab(driver.window_handles[1])
|
||||
|
||||
driver.switch_to.window(driver.window_handles[1])
|
||||
custom_sleep(10)
|
||||
log_error(f"Card {i} Can't be completed. Why MS ?")
|
||||
try:
|
||||
driver.refresh()
|
||||
card_list = driver.find_elements(By.CLASS_NAME, "c-card-content")
|
||||
try_play(driver.title) # go back to the main page
|
||||
try:
|
||||
close_tab(driver.window_handles[1])
|
||||
except Exception as e:
|
||||
debug(e)
|
||||
except Exception as e:
|
||||
debug(e)
|
||||
driver.get("https://rewards.bing.com")
|
||||
|
||||
if "mee-icon-AddMedium" in card_list[i].get_attribute("innerHTML"):
|
||||
driver.execute_script("arguments[0].scrollIntoView();", card_list[i])
|
||||
card_list[i].click()
|
||||
driver.switch_to.window(driver.window_handles[1])
|
||||
custom_sleep(10)
|
||||
log_error(f"Card {i} Can't be completed. Why MS ?")
|
||||
try:
|
||||
try_play(driver.title) # go back to the main page
|
||||
try:
|
||||
close_tab(driver.window_handles[1])
|
||||
except Exception as e:
|
||||
debug(e)
|
||||
except Exception as e:
|
||||
debug(e)
|
||||
driver.get("https://rewards.bing.com")
|
||||
except Exception as err:
|
||||
log_error(err)
|
||||
custom_sleep(3)
|
||||
except Exception as err:
|
||||
log_error(err)
|
||||
custom_sleep(3)
|
||||
|
||||
|
||||
def promo():
|
||||
@ -290,8 +388,124 @@ def promo():
|
||||
custom_sleep(3)
|
||||
|
||||
|
||||
def explore_on_bing(activity: str, config: Config):
|
||||
driver = config.WebDriver.driver
|
||||
|
||||
def search_bing(txt):
|
||||
search_elm = driver.find_element(By.ID, "sb_form_q")
|
||||
send_keys_wait(search_elm, txt)
|
||||
send_keys_wait(search_elm, Keys.ENTER)
|
||||
|
||||
if "lyrics" in activity:
|
||||
search_bing(
|
||||
f"paroles de {['Gata', 'Pyramide', 'Dolce Camara', 'Position', 'Mami Wata'][randint(0, 4)]}") # merci bing copilot pour les titres
|
||||
elif "flight" in activity:
|
||||
search_bing(
|
||||
f"vol {['Paris - New York', 'Londres Amsterdam', 'Bora-Bora Miami', 'Los Angeles Toulouse', 'Rome Dubai'][randint(0, 4)]}")
|
||||
elif "shopping" in activity:
|
||||
search_bing(f"idée cadeau {['Noel', 'Anniversaire'][randint(0, 1)]}")
|
||||
elif "movie" in activity:
|
||||
search_bing(
|
||||
f"Distribution {['Code 8 part 2', 'The Hunger Games: The ballad of Songbirds & Snakes', 'Rebel Moon: Part Two', 'Dune II', 'Wonka'][randint(0, 4)]}")
|
||||
elif "translator" in activity:
|
||||
search_bing(f"traduction {config.wordlist.get_word()} en anglais")
|
||||
elif "map" in activity:
|
||||
search_bing(f"{['Paris', 'Nice', 'Marseille', 'Bordeaux', 'Lyon'][randint(0, 4)]} carte")
|
||||
elif "restaurant" in activity:
|
||||
search_bing(random.choice([
|
||||
"restaurant à proximité",
|
||||
"restaurant pas loin",
|
||||
"manger dans le coin"
|
||||
]))
|
||||
elif "recipe" in activity:
|
||||
search_bing("recette de " + random.choice([
|
||||
"cake aux olives",
|
||||
"tarte tatin",
|
||||
"Kouign Amann",
|
||||
"poulet au Curry",
|
||||
"lasagnes aux épinards et ricotta",
|
||||
"Boeuf Bourguignon",
|
||||
"dessert",
|
||||
"gateau au chocolat",
|
||||
"crêpe sucrée"
|
||||
])) # Merci llama pour les idées
|
||||
elif "currency" in activity:
|
||||
currencies = [
|
||||
"euros",
|
||||
"dollars canadiens",
|
||||
"dollars",
|
||||
"livres",
|
||||
"francs suisses",
|
||||
"roubles",
|
||||
"pesos",
|
||||
"bitcoin",
|
||||
"anciens francs"
|
||||
]
|
||||
chosen = random.sample(currencies, k=2)
|
||||
search_bing(f"convertir {random.randint(2, 120)} {chosen[0]} en {chosen[1]}")
|
||||
elif "weather" in activity:
|
||||
search_bing(f"météo {['Paris', 'Nice', 'Marseille', 'Bordeaux', 'Lyon'][randint(0, 4)]}")
|
||||
elif "packagetracking" in activity:
|
||||
driver.get("https://www.bing.com/?setmkt=en-US&setlang=en-US")
|
||||
search_bing(f"{['fedex', 'UPS'][randint(0, 1)]} package tracking")
|
||||
elif "hotel" in activity:
|
||||
search_bing(f" {['Paris', 'Nice', 'Marseille', 'Bordeaux', 'Lyon'][randint(0, 4)]}")
|
||||
elif "health" in activity:
|
||||
search_bing(f"{['toux', 'douleur dos', 'nez qui coule', 'mal au genoux', 'otite'][randint(0, 4)]}")
|
||||
elif "news" in activity:
|
||||
search_bing(["actualités", "news"][randint(0, 1)])
|
||||
elif "jobs" in activity:
|
||||
search_bing("jobs")
|
||||
elif "realestate" in activity:
|
||||
search_bing(f"appartement à louer {['Paris', 'Nice', 'Marseille', 'Bordeaux', 'Lyon'][randint(0, 4)]}")
|
||||
elif "financemarket" in activity:
|
||||
search_bing(
|
||||
f"cours action {['AIR LIQUIDE', 'Airbus', 'BNP Paribas', 'Michelin', 'Stellantis', 'Vinci'][randint(0, 5)]}")
|
||||
elif "sports" in activity:
|
||||
equipes = random.sample([
|
||||
"Toulon", "toulouse",
|
||||
"stade Rochelais", "castres",
|
||||
"clermont", "perpignan",
|
||||
"aviron bayonnais", "vannes"
|
||||
], k=2)
|
||||
search_bing(f"{['score', 'résultats'][randint(0, 1)]} rugby {equipes[0]} {equipes[1]}")
|
||||
elif "videogames" in activity:
|
||||
search_bing(random.choice([
|
||||
"Minecraft", "GTA V",
|
||||
"Tetris", "PUBG",
|
||||
"Mario Kart 8",
|
||||
"Red Dead Redemption II",
|
||||
"Terraria",
|
||||
"The Witcher",
|
||||
"Animal Crossing",
|
||||
"Pokémon rouge & bleu",
|
||||
"Super Mario Bros",
|
||||
"The legend of Zelda BOTW",
|
||||
"Call of Duty Modern Warfare",
|
||||
"The Witcher III"
|
||||
]))
|
||||
elif "dictionary" in activity:
|
||||
search_bing(f"définition {config.wordlist.get_word()}")
|
||||
elif "election" in activity or "timezonedates" in activity:
|
||||
#! I don't understand the name of "timezonedates" but this seems to be the same activity
|
||||
state = random.choice([
|
||||
"Alaska", "Alabama", "Arkansas", "American Samoa", "Arizona", "California", "Colorado",
|
||||
"Connecticut", "District ", "of Columbia", "Delaware", "Florida", "Georgia", "Guam",
|
||||
"Hawaii", "Iowa", "Idaho", "Illinois", "Indiana", "Kansas", "Kentucky", "Louisiana",
|
||||
"Massachusetts", "Maryland", "Maine", "Michigan", "Minnesota", "Missouri", "Mississippi",
|
||||
"Montana", "North Carolina", "North Dakota", "Nebraska", "New Hampshire", "New Jersey",
|
||||
"New Mexico", "Nevada", "New York", "Ohio", "Oklahoma", "Oregon", "Pennsylvania",
|
||||
"Puerto Rico", "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas",
|
||||
"Utah", "Virginia", "Virgin Islands", "Vermont", "Washington", "Wisconsin",
|
||||
"West Virginia", "Wyoming"
|
||||
])
|
||||
search_bing(f"governor candidates {state} 2024")
|
||||
else:
|
||||
log_error(f"Explore on bing: {activity} not found.")
|
||||
|
||||
|
||||
# Find out which type of action to do
|
||||
def try_play(nom="unknown"):
|
||||
def try_play(nom="unknown", activity=""):
|
||||
driver = config.WebDriver.driver
|
||||
rgpd_popup(config)
|
||||
|
||||
@ -337,7 +551,7 @@ def try_play(nom="unknown"):
|
||||
info("On fidelity page.")
|
||||
fidelity()
|
||||
|
||||
elif wait_until_visible(By.ID, "rqStartQuiz", 5, driver):
|
||||
elif wait_until_visible(By.ID, "rqStartQuiz", 5, driver, raise_error=False):
|
||||
custom_sleep(3)
|
||||
driver.find_element(By.ID, "rqStartQuiz").click() # start the quiz
|
||||
answer_number = driver.page_source.count("rqAnswerOption")
|
||||
@ -347,9 +561,14 @@ def try_play(nom="unknown"):
|
||||
number = driver.page_source.count("rqAnswerOption")
|
||||
warning(f"recovery detected. quiz : {number}")
|
||||
play(number - 1)
|
||||
|
||||
elif "exploreonbing" in activity:
|
||||
info(f"Explore on bing: {activity}")
|
||||
explore_on_bing(activity, config)
|
||||
custom_sleep(uniform(3, 5))
|
||||
elif "Optimisez votre sécurité avec Microsoft" in driver.page_source:
|
||||
info("Still not paying Office 365 ?")
|
||||
else:
|
||||
info(f"Nothing to do on page `{nom}`")
|
||||
info(f"Nothing obvious to do on page `{nom}`.")
|
||||
custom_sleep(uniform(3, 5))
|
||||
|
||||
|
||||
@ -359,23 +578,40 @@ def login_part_1():
|
||||
info("Starting part 1 of login")
|
||||
driver = config.WebDriver.driver
|
||||
driver.get("https://login.live.com")
|
||||
wait_until_visible(By.ID, "i0116", browser=driver)
|
||||
mail_elem = driver.find_element(By.ID, "i0116")
|
||||
send_keys_wait(mail_elem, config.UserCredentials.get_mail())
|
||||
mail_elem.send_keys(Keys.ENTER)
|
||||
try:
|
||||
wait_until_visible(By.ID, "i0116", browser=driver)
|
||||
send_wait_and_confirm(
|
||||
driver.find_element(By.ID, "i0116"),
|
||||
config.UserCredentials.get_mail()
|
||||
)
|
||||
except exceptions.NoSuchElementException:
|
||||
info("Unable to find mail input for login, might be semi-logged-in")
|
||||
|
||||
wait_until_visible(By.ID, "i0118", browser=driver)
|
||||
pwd_elem = driver.find_element(By.ID, "i0118")
|
||||
send_keys_wait(pwd_elem, config.UserCredentials.get_password())
|
||||
pwd_elem.send_keys(Keys.ENTER)
|
||||
custom_sleep(2)
|
||||
send_wait_and_confirm(
|
||||
driver.find_element(By.ID, "i0118"),
|
||||
config.UserCredentials.get_password()
|
||||
)
|
||||
# 2FA
|
||||
if "Entrez le code de sécurité" in driver.page_source:
|
||||
try:
|
||||
a2f_elem = driver.find_element(By.ID, "idTxtBx_SAOTCC_OTC")
|
||||
a2f_elem.send_keys(config.UserCredentials.get_tfa().now())
|
||||
a2f_elem.send_keys(Keys.ENTER)
|
||||
except Exception as err:
|
||||
log_error(err)
|
||||
try:
|
||||
if not wait_until_visible(By.ID, "idTxtBx_SAOTCC_OTC", browser=driver, timeout=5, raise_error=False):
|
||||
custom_sleep(2)
|
||||
return
|
||||
|
||||
tfa = config.UserCredentials.get_tfa()
|
||||
if tfa is None:
|
||||
error("2FA needed but no code available for this account, sending error")
|
||||
raise ValueError("2FA needed but no code available for this account")
|
||||
else:
|
||||
a2f_code = tfa.now()
|
||||
|
||||
info(f"Need 2FA, I have code: {a2f_code}")
|
||||
send_wait_and_confirm(
|
||||
driver.find_element(By.ID, "idTxtBx_SAOTCC_OTC"),
|
||||
a2f_code
|
||||
)
|
||||
except Exception as err:
|
||||
log_error(err)
|
||||
|
||||
|
||||
# Accept all cookies question, and check if the account is locked
|
||||
@ -410,9 +646,19 @@ def login_part_2():
|
||||
# login() tries to login to your Microsoft account.
|
||||
# it uses global variable g._mail and g._password to login
|
||||
def login():
|
||||
def logged_in():
|
||||
driver.get("https://login.live.com")
|
||||
custom_sleep(10)
|
||||
debug(get_domain(driver))
|
||||
if get_domain(driver) == "account.microsoft.com":
|
||||
return True
|
||||
return False
|
||||
|
||||
info("Logging in...")
|
||||
driver = config.WebDriver.driver
|
||||
try:
|
||||
login_part_1()
|
||||
if not logged_in():
|
||||
login_part_1()
|
||||
login_part_2()
|
||||
driver.get("https://rewards.bing.com/")
|
||||
except Banned:
|
||||
@ -537,19 +783,23 @@ def fidelity():
|
||||
pause = driver.find_element(By.CSS_SELECTOR, f'[class="c-action-toggle c-glyph f-toggle glyph-pause"]')
|
||||
pause.click()
|
||||
except Exception as e:
|
||||
error(f"Error while clicking pause. Probably no cards. {e}")
|
||||
return "no cards"
|
||||
debug("No pause button.")
|
||||
|
||||
cartes = driver.find_elements(By.CSS_SELECTOR, f'[ng-repeat="item in $ctrl.transcludedItems"]')
|
||||
nb_cartes = len(cartes)
|
||||
if nb_cartes == 0:
|
||||
warning("No fidelity cards detected")
|
||||
return "No cards."
|
||||
checked_list_all = driver.find_elements(By.CSS_SELECTOR, f'[ng-if="$ctrl.complete"]')
|
||||
for i in range(nb_cartes):
|
||||
cartes[i].click()
|
||||
checked_txt = checked_list_all[i].get_attribute("innerHTML")
|
||||
ok = checked_txt.count("StatusCircleOuter checkmark")
|
||||
total = checked_txt.count("StatusCircleOuter")
|
||||
if (ok != total):
|
||||
if ok != total:
|
||||
elm = driver.find_elements(By.CLASS_NAME, 'clickable-link')[i]
|
||||
if not "moviesandtv" in elm.get_attribute("innerHTML"): # not the film card
|
||||
# legacy code. Should be removed
|
||||
if "moviesandtv" not in elm.get_attribute("innerHTML"): # not the film card
|
||||
elm.click()
|
||||
driver.switch_to.window(driver.window_handles[len(driver.window_handles) - 1])
|
||||
sub_fidelity()
|
||||
@ -610,9 +860,11 @@ def daily_routine(cred: UserCredentials, custom=False):
|
||||
try:
|
||||
if not custom: # custom already is logged in
|
||||
login()
|
||||
|
||||
except Banned:
|
||||
log_error("This account is locked.")
|
||||
return
|
||||
raise Banned()
|
||||
|
||||
except Identity:
|
||||
log_error("This account has an issue.")
|
||||
return
|
||||
@ -665,53 +917,53 @@ def json_start(json_entry, cred: UserCredentials):
|
||||
config.WebDriver.set_pc_driver(create_driver())
|
||||
config.WebDriver.switch_to_driver("PC")
|
||||
driver = config.WebDriver.driver
|
||||
try:
|
||||
if str(account_id) in json_entry["unban"]:
|
||||
login_part_1()
|
||||
info("\nGO TO example.com TO PROCEED or wait 1200 secs.")
|
||||
for _ in range(1200):
|
||||
sleep(1)
|
||||
if driver.current_url == "https://example.com/":
|
||||
info("proceeding")
|
||||
break
|
||||
else:
|
||||
login()
|
||||
except KeyError:
|
||||
if "unban" in json_entry and str(account_id) in json_entry["unban"]:
|
||||
login()
|
||||
info("\nGO TO example.com TO PROCEED or wait 1200 secs.")
|
||||
for _ in range(1200):
|
||||
sleep(1)
|
||||
if driver.current_url == "https://example.com/":
|
||||
info("proceeding")
|
||||
break
|
||||
else:
|
||||
login()
|
||||
try:
|
||||
if str(account_id) in json_entry["tout"]:
|
||||
daily_routine(cred)
|
||||
daily_routine(cred, True)
|
||||
|
||||
else:
|
||||
try:
|
||||
if str(account_id) in json_entry["daily"]:
|
||||
try:
|
||||
all_cards()
|
||||
except Exception as e:
|
||||
log_error(e)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
if str(account_id) in json_entry["pc"]:
|
||||
try:
|
||||
bing_pc_search()
|
||||
except Exception as e:
|
||||
log_error(e)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
if str(account_id) in json_entry["mobile"]:
|
||||
try:
|
||||
bing_mobile_search(cred)
|
||||
except Exception as e:
|
||||
log_error(e)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
if str(account_id) in json_entry["daily"]:
|
||||
try:
|
||||
all_cards()
|
||||
except Exception as e:
|
||||
log_error(e)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
if str(account_id) in json_entry["pc"]:
|
||||
try:
|
||||
bing_pc_search()
|
||||
except Exception as e:
|
||||
log_error(e)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
if str(account_id) in json_entry["mobile"]:
|
||||
try:
|
||||
bing_mobile_search(cred)
|
||||
except Exception as e:
|
||||
log_error(e)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
log_points()
|
||||
except Exception as e:
|
||||
error(f"CustomStart {e}")
|
||||
try:
|
||||
log_points()
|
||||
except Exception as e:
|
||||
error(f"CustomStart {e}")
|
||||
|
||||
driver.close()
|
||||
cred.next_account()
|
||||
account_id += 1
|
||||
@ -728,13 +980,17 @@ def default_start():
|
||||
while config.UserCredentials.is_valid():
|
||||
custom_sleep(1)
|
||||
info("Starting and configuring driver.")
|
||||
config.WebDriver.set_pc_driver(create_driver())
|
||||
try:
|
||||
config.WebDriver.set_pc_driver(create_driver())
|
||||
except:
|
||||
info("Could not configure driver. Trying to update it.")
|
||||
subprocess.Popen(["python3", "/app/MsRewards-Reborn/modules/Tools/update_chrome.py"])
|
||||
config.WebDriver.set_pc_driver(create_driver())
|
||||
config.WebDriver.switch_to_driver("PC")
|
||||
info("Driver started.")
|
||||
config.WebDriver.pc_driver.implicitly_wait(3)
|
||||
try:
|
||||
wait_time = uniform(1200, 3600)
|
||||
wait_time = 10 # the display closes when I ctrl + c
|
||||
info(f"Waiting for {round(wait_time / 60)}min before starting")
|
||||
custom_sleep(wait_time)
|
||||
daily_routine(config.UserCredentials)
|
||||
@ -744,12 +1000,16 @@ def default_start():
|
||||
config.WebDriver.pc_driver.quit()
|
||||
config.display.stop()
|
||||
break
|
||||
except Banned:
|
||||
warning("this account is banned. Switching to next account")
|
||||
except Exception as e:
|
||||
log_error(f"Error not caught. Skipping this account. " + format_error(e))
|
||||
critical(f"Error not caught. Skipping this account. {e}")
|
||||
config.WebDriver.pc_driver.quit()
|
||||
|
||||
config.UserCredentials.next_account()
|
||||
finally:
|
||||
config.UserCredentials.next_account()
|
||||
|
||||
config.display.stop()
|
||||
|
||||
|
||||
@ -758,7 +1018,6 @@ def log_error(msg):
|
||||
|
||||
|
||||
def check_updated():
|
||||
debug(f"updated: {config.has_been_updated()}")
|
||||
if config.has_been_updated():
|
||||
config.discord.wh.send(f"Updated to {config.version}", username="update",
|
||||
avatar_url="https://cdn-icons-png.flaticon.com/512/1688/1688988.png")
|
||||
|
13
build.sh
13
build.sh
@ -1 +1,12 @@
|
||||
sudo docker build --no-cache --network host -t msrewards . && sudo docker run -d --restart unless-stopped -p 1234:1234 -p 2345:2345 -ti --shm-size=2gb --name MsRewards msrewards
|
||||
#!/bin/bash
|
||||
|
||||
docker-do () { # Check if sudo needs to be used
|
||||
if id -nG "$(whoami)" | grep -qw "docker"; then
|
||||
docker $@
|
||||
else
|
||||
sudo docker $@
|
||||
fi
|
||||
}
|
||||
|
||||
docker-do build --network host -t msrewards .
|
||||
docker-do run -d --restart unless-stopped -p 1234:1234 -p 2345:2345 -ti --shm-size=2gb --name MsRewards msrewards
|
||||
|
13
clean.sh
Executable file
13
clean.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
docker-do () { # Check if sudo needs to be used
|
||||
if id -nG "$(whoami)" | grep -qw "docker"; then
|
||||
docker $@
|
||||
else
|
||||
sudo docker $@
|
||||
fi
|
||||
}
|
||||
|
||||
docker-do stop MsRewards
|
||||
docker-do rm MsRewards
|
||||
docker-do image rm msrewards
|
@ -46,6 +46,7 @@ server {
|
||||
proxy_pass "http://127.0.0.1:6666";
|
||||
chunked_transfer_encoding off;
|
||||
proxy_buffering off;
|
||||
add_header X-Accel-Buffering no;
|
||||
}
|
||||
|
||||
}
|
||||
@ -59,7 +60,6 @@ sqlite3 /app/MsRewards-Reborn/MsRewards.db "CREATE TABLE comptes (id INTEGER PRI
|
||||
printf "\nconfigurating grafana\n"
|
||||
|
||||
cp /app/MsRewards-Reborn/config/grafana.ini /etc/grafana/
|
||||
grafana-cli plugins install frser-sqlite-datasource
|
||||
|
||||
printf "setting up default dashboard"
|
||||
cp /app/MsRewards-Reborn/config/Stats-dashbord.json /usr/share/grafana/public/dashboards/home.json
|
||||
|
1459
config/grafana.ini
1459
config/grafana.ini
File diff suppressed because it is too large
Load Diff
12
hooks/bump.sh
Executable file
12
hooks/bump.sh
Executable file
@ -0,0 +1,12 @@
|
||||
file=version
|
||||
|
||||
if [ "$(git diff HEAD version | wc -w)" -eq 0 ]
|
||||
then
|
||||
echo 'updating minor version'
|
||||
new_path=$(cat $file | awk -F . '{print $1"."$2"."$3+1 }')
|
||||
echo $new_path > $file
|
||||
git add version
|
||||
else
|
||||
echo 'Version have been updated manually'
|
||||
fi
|
||||
echo Version: $(cat $file)
|
1
hooks/pre-commit
Executable file
1
hooks/pre-commit
Executable file
@ -0,0 +1 @@
|
||||
sh hooks/bump.sh
|
@ -22,7 +22,8 @@ class Config:
|
||||
proxy = json.load(inFile)
|
||||
with open("/app/MsRewards-Reborn/user_data/configs.json", "r") as inFile:
|
||||
config = json.load(inFile)
|
||||
|
||||
with open("/app/MsRewards-Reborn/user_data/version", "r") as inFile:
|
||||
version = inFile.readline()
|
||||
"""
|
||||
setup standalone stuff
|
||||
"""
|
||||
@ -31,10 +32,9 @@ class Config:
|
||||
self.json_entry = args.json.replace("'", "\"")
|
||||
self.wordlist = WordList("/usr/share/dict/french")
|
||||
self.vnc = args.vnc
|
||||
self.version = args.update_version
|
||||
self.WebDriver = Driver()
|
||||
self.display = None
|
||||
|
||||
self.version = version
|
||||
"""
|
||||
setup UserCredential
|
||||
"""
|
||||
@ -50,9 +50,14 @@ class Config:
|
||||
"""
|
||||
self.discord = DiscordConfig()
|
||||
self.discord.avatar_url = settings["avatarlink"]
|
||||
self.discord.wh_link = discord[config[args.config]["discord"]]["errorsL"]
|
||||
|
||||
if self.discord.wh_link != "":
|
||||
if (
|
||||
"discord" in config[args.config]
|
||||
and config[args.config]["discord"] in discord
|
||||
and "errorsL" in discord[config[args.config]["discord"]]
|
||||
and discord[config[args.config]["discord"]]["errorsL"] != ""
|
||||
):
|
||||
self.discord.wh_link = discord[config[args.config]["discord"]]["errorsL"]
|
||||
self.discord.wh = Webhook.from_url(self.discord.wh_link, adapter=RequestsWebhookAdapter())
|
||||
else:
|
||||
self.discord.wh = FakeWebHook()
|
||||
@ -76,4 +81,11 @@ class Config:
|
||||
self.display = display
|
||||
|
||||
def has_been_updated(self):
|
||||
return self.version != "None"
|
||||
with open('/app/MsRewards-Reborn/version', "r") as inFile:
|
||||
in_file_content = inFile.readlines()
|
||||
if self.version != in_file_content[0]:
|
||||
self.version = in_file_content[0]
|
||||
with open('/app/MsRewards-Reborn/user_data/version', "w") as outFile:
|
||||
outFile.write(self.version)
|
||||
return True
|
||||
return False
|
||||
|
@ -9,6 +9,6 @@ class DiscordConfig:
|
||||
|
||||
|
||||
class FakeWebHook:
|
||||
def send(self, *args):
|
||||
debug(f"Used a webhook call without webhook url with {args}")
|
||||
def send(self, *args, **kwargs):
|
||||
debug(f"Used a webhook call without webhook url with {args} {kwargs}")
|
||||
|
||||
|
@ -30,7 +30,7 @@ class DiscordLogger:
|
||||
)
|
||||
file = File("screenshot.png")
|
||||
embed.set_image(url="attachment://screenshot.png")
|
||||
embed.set_footer(text=self.config.UserCredentials.creds.get_mail())
|
||||
embed.set_footer(text=self.config.UserCredentials.get_mail() + " - " + self.config.WebDriver.current_driver())
|
||||
|
||||
self.config.discord.wh.send(embed=embed, username="error", file=file, avatar_url=self.config.discord.avatar_url)
|
||||
self.config.discord.wh.send(username="error", file=File("page.html"), avatar_url=self.config.discord.avatar_url)
|
||||
|
@ -11,12 +11,15 @@ class Driver:
|
||||
self.mobile_driver = mobile_driver
|
||||
|
||||
def switch_to_driver(self, driver: str):
|
||||
match driver:
|
||||
case "pc" | "PC" | "Pc":
|
||||
match driver.lower():
|
||||
case "pc":
|
||||
self.driver = self.pc_driver
|
||||
|
||||
case "mobile" | "Mobile":
|
||||
case "mobile":
|
||||
self.driver = self.mobile_driver
|
||||
|
||||
case _:
|
||||
raise ValueError("The driver must be either pc or mobile")
|
||||
|
||||
def current_driver(self):
|
||||
return "PC" if self.pc_driver == self.driver else "Mobile"
|
||||
|
@ -30,8 +30,9 @@ class UserCredentials:
|
||||
|
||||
def get_tfa(self):
|
||||
if not self.tfa_enable():
|
||||
warning("Warning: TFA is not enabled. Calling get_tfa is an expected behaviour.")
|
||||
return TOTP(self.data[self.current]["tfa"])
|
||||
warning("Warning: TFA is not enabled. Can't get a TFA code.")
|
||||
return None
|
||||
return TOTP(self.data[self.current]["2fa"])
|
||||
|
||||
def next_account(self):
|
||||
self.current += 1
|
||||
@ -41,4 +42,5 @@ class UserCredentials:
|
||||
debug("No new credentials.")
|
||||
|
||||
def is_valid(self):
|
||||
return self.current < self.total
|
||||
return (self.current < self.total
|
||||
and self.get_mail() != "" and self.get_mail is not None)
|
||||
|
8
modules/Tools/generate_error.py
Normal file
8
modules/Tools/generate_error.py
Normal file
@ -0,0 +1,8 @@
|
||||
import undetected_chromedriver as uc
|
||||
from pyvirtualdisplay.smartdisplay import SmartDisplay
|
||||
|
||||
display = SmartDisplay(size=(1920, 1080))
|
||||
display.start()
|
||||
driver = uc.Chrome()
|
||||
driver.close()
|
||||
driver.close()
|
@ -30,7 +30,7 @@ class ColoredFormatter(logging.Formatter):
|
||||
|
||||
# Set up the root logger
|
||||
root_logger = logging.getLogger(__name__)
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
root_logger.setLevel(logging.INFO)
|
||||
|
||||
# Create a console handler and set the formatter
|
||||
ch = logging.StreamHandler()
|
||||
|
@ -10,7 +10,7 @@ def get_domain(driver):
|
||||
|
||||
def custom_sleep(temps):
|
||||
try:
|
||||
if True: # todo: change this awful condition
|
||||
if False: # todo: change this awful condition
|
||||
points = ["⢿", "⣻", "⣽", "⣾", "⣷", "⣯", "⣟", "⡿"]
|
||||
passe = 0
|
||||
for _ in range(int(temps)):
|
||||
|
50
modules/Tools/update_chrome.py
Normal file
50
modules/Tools/update_chrome.py
Normal file
@ -0,0 +1,50 @@
|
||||
import requests
|
||||
import re
|
||||
from packaging import version
|
||||
import subprocess
|
||||
|
||||
from logger import critical, info, error
|
||||
|
||||
errorMessage = subprocess.run(['python3', '/app/MsRewards-Reborn/modules/Tools/generate_error.py'], check=False, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE).stderr.decode("utf-8")
|
||||
|
||||
versionPattern = "This version of ChromeDriver only supports Chrome version ([0-9]+)"
|
||||
|
||||
try:
|
||||
versionN = re.search(versionPattern, errorMessage)[1]
|
||||
except Exception as e:
|
||||
critical("Can't get version number from error")
|
||||
error(e)
|
||||
info("error message")
|
||||
info(errorMessage)
|
||||
exit(0)
|
||||
|
||||
info(f"Needed version : '{versionN}'")
|
||||
|
||||
downloadUrl = "http://mirror.cs.uchicago.edu/google-chrome/pool/main/g/google-chrome-stable/"
|
||||
r = requests.get(downloadUrl)
|
||||
|
||||
content = r.text
|
||||
|
||||
exactVersionList = re.findall(f"(google-chrome-stable_({versionN}.[0-9.]+)[^<^>^\"]+)", content)
|
||||
|
||||
try:
|
||||
best = exactVersionList[0]
|
||||
except Exception as e:
|
||||
critical("No version matches required version")
|
||||
error(e)
|
||||
exit(0)
|
||||
|
||||
for i in exactVersionList:
|
||||
if version.parse(i[1]) > version.parse(best[1]):
|
||||
best = i
|
||||
|
||||
chromeDebURL = f"http://mirror.cs.uchicago.edu/google-chrome/pool/main/g/google-chrome-stable/{best[0]}"
|
||||
info(f"chrome deb URL : {chromeDebURL}")
|
||||
info("downloading chrome")
|
||||
|
||||
subprocess.call(['wget', "-O", "/tmp/chrome.deb", chromeDebURL])
|
||||
info("Chrome deb downloaded. Installing chrome")
|
||||
|
||||
subprocess.call(["dpkg", "-i", "/tmp/chrome.deb"])
|
||||
info("Chrome installed")
|
@ -1,7 +1,9 @@
|
||||
import json
|
||||
import os
|
||||
from random import uniform
|
||||
|
||||
from selenium.common import TimeoutException
|
||||
from selenium.webdriver import ActionChains, Keys
|
||||
from selenium.webdriver import Keys
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions
|
||||
from selenium.webdriver.support.wait import WebDriverWait
|
||||
@ -10,33 +12,40 @@ from modules.Tools.logger import debug
|
||||
from modules.Tools.tools import *
|
||||
|
||||
|
||||
def set_language(ldriver):
|
||||
ldriver.get("chrome://settings/languages")
|
||||
action = ActionChains(ldriver)
|
||||
action.reset_actions()
|
||||
# select language
|
||||
x_coord = 1200
|
||||
y_coord = 150
|
||||
action.move_by_offset(x_coord, y_coord).click().perform()
|
||||
sleep(0.5)
|
||||
# scroll down
|
||||
action.reset_actions()
|
||||
elm = ldriver.find_element(By.XPATH, "/html/body")
|
||||
ActionChains(ldriver) \
|
||||
.send_keys("french") \
|
||||
.pause(0.5) \
|
||||
.send_keys(Keys.TAB + Keys.TAB + Keys.ENTER + Keys.TAB + Keys.TAB + Keys.ENTER) \
|
||||
.perform()
|
||||
x_coord = 1163
|
||||
y_coord = 717
|
||||
action.move_by_offset(x_coord, y_coord).click().perform()
|
||||
# put to the top
|
||||
sleep(0.5)
|
||||
action.reset_actions()
|
||||
x_coord = 1257
|
||||
y_coord = 328
|
||||
action.move_by_offset(x_coord, y_coord).click().perform()
|
||||
action.click().perform()
|
||||
def init_profile(mail, mobile=False):
|
||||
if not mobile:
|
||||
chrome_profile_dir = "/app/MsRewards-Reborn/user_data/profile/" + mail
|
||||
else:
|
||||
chrome_profile_dir = "/app/MsRewards-Reborn/user_data/profile/mobile-" + mail
|
||||
|
||||
os.makedirs(chrome_profile_dir, exist_ok=True)
|
||||
|
||||
preferences_file = os.path.join(chrome_profile_dir, "Default", "Preferences")
|
||||
if not os.path.exists(preferences_file):
|
||||
os.makedirs(os.path.join(chrome_profile_dir, "Default"), exist_ok=True)
|
||||
with open(preferences_file, "w") as f:
|
||||
json.dump(
|
||||
{
|
||||
"intl": {
|
||||
"accept_languages": "fr-FR,en-US,en",
|
||||
"selected_languages": "fr-FR,en-US,en"
|
||||
}
|
||||
}, f
|
||||
)
|
||||
else:
|
||||
with open(preferences_file, "r") as f:
|
||||
settings = json.load(f)
|
||||
|
||||
if "intl" not in settings:
|
||||
settings["intl"] = {}
|
||||
|
||||
settings["intl"]["accept_languages"] = "fr-FR,en-US,en"
|
||||
settings["intl"]["selected_languages"] = "fr-FR,en-US,en"
|
||||
|
||||
with open(preferences_file, "w") as f:
|
||||
json.dump(settings, f)
|
||||
|
||||
return chrome_profile_dir
|
||||
|
||||
|
||||
# Deal with RGPD popup as well as some random popup like 'are you satisfied' one
|
||||
@ -61,12 +70,18 @@ def send_keys_wait(element, keys: str) -> None:
|
||||
sleep(uniform(0.1, 0.3))
|
||||
|
||||
|
||||
def send_wait_and_confirm(element, keys: str) -> None:
|
||||
send_keys_wait(element, keys)
|
||||
element.send_keys(Keys.ENTER)
|
||||
|
||||
|
||||
# Wait for the presence of the element identifier or [timeout]s
|
||||
def wait_until_visible(search_by: str, identifier: str, timeout: int = 20, browser=None) -> bool:
|
||||
def wait_until_visible(search_by: str, identifier: str, timeout: int = 20, browser=None, raise_error=True) -> bool:
|
||||
try:
|
||||
WebDriverWait(browser, timeout).until(
|
||||
expected_conditions.visibility_of_element_located((search_by, identifier)), "element not found")
|
||||
return True
|
||||
except TimeoutException as e:
|
||||
error(f"element {identifier} not found after {timeout}s")
|
||||
f = error if raise_error else debug
|
||||
f(f"element {identifier} not found after {timeout}s")
|
||||
return False
|
||||
|
1
user_data/version
Normal file
1
user_data/version
Normal file
@ -0,0 +1 @@
|
||||
0.0.0
|
Loading…
Reference in New Issue
Block a user