funk-manager/server.py

460 lines
16 KiB
Python

import sqlite3
from flask import g
from flask import Flask
app = Flask(__name__)
from functools import wraps
from flask import request, Response
from flask import render_template, redirect
from funkapi import FunkAPI
import json
import time
import datetime
from dateutil.relativedelta import relativedelta
from apscheduler.schedulers.background import BackgroundScheduler
import os
import atexit
import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug("Start")
DATABASE = os.getenv("FUNK_DATABASE", 'database.db')
USERNAME = os.getenv("FUNK_USER", 'admin')
PASSWORD = os.getenv("FUNK_PASS", 'geheim')
ENABLED_AUTH = os.getenv("FUNK_AUTH", False)
UPDATE_INTERVAL = int(os.getenv("FUNK_UPDATE_INTERVAL", 60*10))
plans = []
plans.append({"name": "Unlimit", "number": "8", "canBeBooked": True})
plans.append({"name": "1 GB", "number": "9", "canBeBooked": True})
plans.append({"name": "Break", "number": "42", "canBeBooked": True})
plans.append({"name": "Unlimit - First Day", "number": "40", "canBeBooked": False})
plans.append({"name": "Unlimit - Immidiately", "number": "43", "canBeBooked": False})
#Helper
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
return db
def check_auth(username, password):
"""This function is called to check if a username /
password combination is valid.
"""
return username == USERNAME and password == PASSWORD
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
if not ENABLED_AUTH:
return f(*args, **kwargs)
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
# Functions
def function_cron():
print("Run Cron")
with app.app_context():
print("Debug Mode:" + str(int(app.debug)))
if app.debug == False:
function_updateAccountsAtFunk()
else:
print("Cron don't run in debug mode")
def function_updateAllAccounts():
accounts = function_getAccounts()
for account in accounts:
function_updateAccount(account["number"])
def function_updateAccount(number, api = None):
cur = get_db().cursor()
if api == None:
cur.execute("SELECT mail, password FROM accounts WHERE `number` = '%s'" % (number))
res = cur.fetchone()
api = FunkAPI(res[0], res[1])
usage = api.getData()["data"]["me"]["customerProducts"][0]["mobileNumbers"][0]["usage"]["usedDataPercentage"]
plan = getCurrentPlan(api)
if plan == None:
plan = 42;
else:
plan = plan["productServiceId"]
cur.execute("INSERT INTO updates VALUES (%s, %s, '%s', '%s')" % (
number,
int(time.time()),
plan,
usage
))
get_db().commit()
def function_speacialDays(number):
cur = get_db().cursor()
cur.execute("SELECT day, month, year, plan FROM special WHERE number = %s ORDER BY year, month, day" % number)
res = cur.fetchall()
days = []
for r in res:
day = {}
day["day"] = str(r[0])+"."+str(r[1])+"."+str(r[2])
day["plan"] = function_getPlanName(r[3])
days.append(day)
return days
def getCurrentPlan(api, now = datetime.datetime.now(datetime.timezone.utc)):
currentPlan = None
for plan in api.getData(refresh=False)["data"]["me"]["customerProducts"][0]["tariffs"]:
planStart = datetime.datetime.strptime(plan["starts"], "%Y-%m-%dT%H:%M:%S.%f%z")
if planStart > now:
continue
currentPlan = plan
return currentPlan
def function_getPlanName(planNumber):
for plan in plans:
if plan["number"] == planNumber:
return plan
return False
def function_getAccounts(includePassword = False):
cur = get_db().cursor()
cur.execute("SELECT number, mail, password, owner, defaultPlan FROM accounts")
res = cur.fetchall()
accounts = []
for accountData in res:
lastUpdate = cur.execute("SELECT updateTime, plan, dataUsed FROM updates WHERE number = '%s' ORDER BY updateTime DESC LIMIT 1" % (accountData[0])).fetchone()
account = {}
account["number"] = accountData[0]
account["mail"] = accountData[1]
if includePassword:
account["password"] = accountData[2]
account["owner"] = accountData[3]
account["defaultPlan"] = function_getPlanName(accountData[4])
account["currentPlan"] = function_getPlanName(lastUpdate[1])
account["dataUsed"] = lastUpdate[2]
account["lastUpdate"] = lastUpdate[0]
accounts.append(account)
return accounts
def function_getAccount(number, includePassword = False):
cur = get_db().cursor()
cur.execute("SELECT number, mail, password, owner, defaultPlan FROM accounts WHERE number = %s" % (number))
accountData = cur.fetchone()
lastUpdate = cur.execute("SELECT updateTime, plan, dataUsed FROM updates WHERE number = '%s' ORDER BY updateTime DESC LIMIT 1" % (accountData[0])).fetchone()
account = {}
account["number"] = accountData[0]
account["mail"] = accountData[1]
if includePassword:
account["password"] = accountData[2]
account["owner"] = accountData[3]
account["defaultPlan"] = function_getPlanName(accountData[4])
account["currentPlan"] = function_getPlanName(lastUpdate[1])
account["dataUsed"] = lastUpdate[2]
account["lastUpdate"] = lastUpdate[0]
return account
def function_getPlanForDay(number, year, month, day):
cur = get_db().cursor()
dayPlan = cur.execute("SELECT plan FROM special WHERE number = %s AND day = %s AND month = %s AND year = %s" % (number, day, month, year)).fetchone()
if dayPlan:
return dayPlan[0]
else:
defaultPlan = cur.execute("SELECT defaultPlan FROM accounts WHERE number = %s" % (number)).fetchone()
return defaultPlan[0]
def function_updateAccountsAtFunk():
logging.debug("Send Updates to Func")
accounts = function_getAccounts(True)
tomorrow = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1)
today = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1)
for account in accounts:
logging.debug("Update for Account: "+str(account["number"]))
api = FunkAPI(account["mail"], account["password"])
function_updateAccount(account["number"], api) # We already have the API with the Data, so we can write them in the update table
planedPlan = function_getPlanForDay(account["number"], tomorrow.year, tomorrow.month, tomorrow.day)
plan = getCurrentPlan(api, tomorrow)
currentPlan = getCurrentPlan(api)
if currentPlan == 42: # Wenn aktuell eine pause ist darf das update erst am selben tag erfolgen wie der neue Plan gültug ist
logging.debug("Account is in a Break, updates will be called at the day the new Plan should work")
planedPlan = function_getPlanForDay(account["number"], today.year, today.month, today.day)
if(planedPlan == 42):
logging.debug("Today Day is already at the currect plan: "+str(planedPlan))
print("Nothing to Change, plan already set")
else:
logging.debug("Next day should be updatedt")
if planedPlan == "8": #Change to unlimit
api.orderUnlimitedPlan()
print("Switch to Unlimit")
elif planedPlan == "9": #Change to 1GB
api.order1GBPlan()
print("Switch to 1GB")
elif planedPlan == "42": #Change to break
api.startPause()
print("Switch to break")
else:
print("Cant update plan!!! ERROR")
else:
logging.debug("Account it not on break, next Day can be set")
if(plan["productServiceId"] == planedPlan):
logging.debug("Next Day is already at the currect plan: "+str(planedPlan))
print("Nothing to Change, plan already set")
else:
logging.debug("Next day should be updatedt")
print("Update")
if planedPlan == "8": #Change to unlimit
api.orderUnlimitedPlan()
print("Switch to Unlimit")
elif planedPlan == "9": #Change to 1GB
api.order1GBPlan()
print("Switch to 1GB")
elif planedPlan == "42": #Change to break
api.startPause()
print("Switch to break")
else:
print("Cant update plan!!! ERROR")
#Routes
@app.route("/")
@requires_auth
def hello():
resData = function_getAccounts()
plans = {}
plans["1 GB"] = 0;
plans["Unlimit"] = 0;
plans["Unlimit - First Day"] = 0;
plans["Unlimit - Immidiately"] = 0;
plans["Break"] = 0
for account in resData:
plans[account["currentPlan"]["name"]] = plans[account["currentPlan"]["name"]] + 1;
return render_template("dashboard.html", debug=app.debug, plans=plans)
@app.route('/setup', methods=['GET'])
@requires_auth
def setupDB():
cur = get_db().cursor()
cur.execute('''CREATE TABLE accounts (number integer, mail text, password text, owner text, defaultPlan text)''')
cur.execute('''CREATE TABLE updates (number integer, updateTime integer, plan text, dataUsed integer)''')
cur.execute('''CREATE TABLE special (number integer, day integer, month integer, year integer, plan text)''')
cur.execute('''CREATE TABLE run_actions (year integer, month integer, day integer)''')
get_db().commit()
return "Accounts"
# GUI
@app.route('/sendToFunk', methods=['GET'])
@requires_auth
def sendToFunkByGui():
function_updateAccountsAtFunk()
return "Update send to funk"
@app.route("/accounts")
@requires_auth
def gui_liste():
return render_template("list.html", debug=app.debug, accounts=function_getAccounts(), currentTime=int(time.time()))
@app.route("/account/<number>")
@requires_auth
def gui_account(number):
account = function_getAccount(number)
special_days = function_speacialDays(number)
return render_template("account.html", debug=app.debug, account=account, plans=plans, special_days=special_days, currentTime=int(time.time()))
@app.route("/account/<number>/statistics")
@requires_auth
def gui_statistic(number):
cur = get_db().cursor()
ende = datetime.datetime.now()
start = ende - relativedelta(months=1)
delta = ende - start
dayList = []
for i in range(delta.days + 1):
day = {}
date = start + datetime.timedelta(days=i)
dayStart = datetime.datetime(date.year, date.month, date.day, 0, 0, 0)
dayEnde = datetime.datetime(date.year, date.month, date.day, 23, 59, 59)
day["date"] = str(date.day)+"."+str(date.month)+"."+str(date.year)
lastUpdate = cur.execute("SELECT dataUsed, updateTime FROM `updates` WHERE `number` = %s AND `updateTime` > %s AND `updateTime` < %s ORDER BY `updateTime` DESC" % (number, int(dayStart.timestamp()), int(dayEnde.timestamp()))).fetchone()
if lastUpdate == None:
day["usage"] = 0
else:
day["usage"] = round(lastUpdate[0], 4);
dayList.append(day)
return render_template("statistics.html", debug=app.debug, dayList=dayList)
@app.route("/account/<number>/special", methods=['POST'])
@requires_auth
def special_days(number):
start = datetime.datetime(int(request.form["from_year"]), int(request.form["from_month"]), int(request.form["from_day"]), 0, 0, 0)
ende = datetime.datetime(int(request.form["to_year"]), int(request.form["to_month"]), int(request.form["to_day"]), 0, 0, 0)
delta = ende - start
cur = get_db().cursor()
for i in range(delta.days + 1):
date = start + datetime.timedelta(days=i)
if request.form["plan"] == "Default":
cur.execute("DELETE FROM special WHERE number = %s AND year = %s AND month = %s AND day = %s" % (number, date.year, date.month, date.day))
else:
cur.execute("DELETE FROM special WHERE number = %s AND year = %s AND month = %s AND day = %s" % (number, date.year, date.month, date.day))
cur.execute("INSERT INTO special VALUES (%s, %s, %s, %s, '%s')" % (number, date.day, date.month, date.year, request.form["plan"]))
get_db().commit();
return redirect("/account/"+str(number), code=302)
@app.route("/account/<number>/days")
@requires_auth
def list_days(number):
start = datetime.datetime.now()
ende = start + relativedelta(months=2)
delta = ende - start
dayList = []
for i in range(delta.days + 1):
day = {}
date = start + datetime.timedelta(days=i)
day["date"] = str(date.day)+"."+str(date.month)+"."+str(date.year)
day["plan"] = function_getPlanName(function_getPlanForDay(number, date.year, date.month, date.day))
dayList.append(day)
return render_template("list_days.html", debug=app.debug, dayList=dayList)
@app.route("/account/add", methods=['GET'])
@requires_auth
def add_account_gui():
return render_template("account_add.html")
@app.route("/account/add", methods=['POST'])
@requires_auth
def add_account_gui_save():
r = addAccount()
return redirect("/accounts", code=302)
@app.route("/log", methods=['GET'])
@requires_auth
def log_show_gui():
f = open("example.log")
content = f.readlines()
f.close()
return render_template("log.html", content=content, debug=app.debug)
# API
@app.route('/api/accounts', methods=['GET'])
@requires_auth
def listAccounts():
resData = function_getAccounts()
response = {"success": True, "data": resData}
return json.dumps(response)
@app.route('/api/account/<number>/update', methods=['GET'])
@requires_auth
def updateAccount(number):
function_updateAccount(number)
response = {"success": True}
return json.dumps(response)
@app.route('/api/account/<number>/changeDafault', methods=['POST'])
@requires_auth
def changeDefault(number):
cur = get_db().cursor()
default = request.form["plan"]
cur.execute("UPDATE accounts SET `defaultPlan` = '%s' WHERE `number` = %s" % (default, number))
get_db().commit();
response = {"success": True}
return json.dumps(response)
@app.route('/api/accounts', methods=['POST'])
@requires_auth
def addAccount():
cur = get_db().cursor()
mail = request.form["mail"]
password = request.form["password"]
response = {}
response["success"] = False
response["msg"] = ""
try:
api = FunkAPI(mail, password)
personalData = api.getPersonalInfo()
except Exception:
response["msg"] = "Autherized failed, wrong mail/password?"
return json.dumps(response)
plan = getCurrentPlan(api)["productServiceId"]
name = personalData["firstName"]+" "+personalData["lastName"]
defaultPlan = 42
for plan in plans:
if plan["number"] == plan:
defaultPlan = plan
# getUsage
data = api.getData()
number = data["data"]["me"]["customerProducts"][0]["mobileNumbers"][0]["number"]
#number integer, mail text, password text, owner text, defaultPlan text
cur.execute("INSERT INTO accounts VALUES ('%s', '%s', '%s', '%s', '%s')" % (
number,
mail,
password,
name,
defaultPlan
))
get_db().commit()
function_updateAccount(number)
response["success"] = True
response["data"] = {"name": name}
return json.dumps(response)
#SQLITE
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
atexit.register(lambda: scheduler.shutdown())
def print_date_time():
print(time.strftime("%A, %d. %B %Y %I:%M:%S %p"))
if app.debug == False:
print("Start cron");
print("Cron interval: "+str(UPDATE_INTERVAL))
scheduler = BackgroundScheduler()
scheduler.add_job(func=function_cron, trigger="interval", seconds=(UPDATE_INTERVAL))
scheduler.start()
else:
print("Dont start crons in Debug Mode!")
if __name__ == '__main__':
app.run(debug=False,host='0.0.0.0')