First Commit

This commit is contained in:
Kekskurse 2019-07-06 11:34:24 +02:00
commit 12e5e43f5c
9 changed files with 638 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
__pycache__
*.pyc
database.db

6
Dockerfile Normal file
View file

@ -0,0 +1,6 @@
FROM python:3.7.3
COPY ./ ./
RUN pip install --no-cache-dir -r requirements.txt
CMD [ "python", "./server.py" ]

2
requirements.txt Normal file
View file

@ -0,0 +1,2 @@
freenet-funk-api==0.1.4
Flask==1.1.0

332
server.py Normal file
View file

@ -0,0 +1,332 @@
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
import os
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)
#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_updateAccount(number):
cur = get_db().cursor()
cur.execute("SELECT mail, password FROM accounts WHERE `number` = '%s'" % (number))
res = cur.fetchone()
api = FunkAPI(res[0], res[1])
plan = api.getCurrentPlan()["productServiceInfo"]["marketingInfo"]["name"]
usage = api.getData()["data"]["me"]["customerProducts"][0]["mobileNumbers"][0]["usage"]["usedDataPercentage"]
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"] = r[3]
days.append(day)
return days
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"] = accountData[4]
account["currentPlan"] = 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"] = accountData[4]
account["currentPlan"] = 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]
@app.route("/updateAll")
@requires_auth
def function_updateAccountsAtFunk():
cur = get_db().cursor()
today = datetime.datetime.now()
done = cur.execute("SELECT count(*) FROM run_actions WHERE year = %s AND month = %s AND day = %s" % (today.year, today.month, today.day)).fetchone()
if done[0] == 0:
numbers = cur.execute("SELECT number FROM accounts").fetchall()
tomorrow = datetime.date.today() + datetime.timedelta(days=1)
for number in numbers:
print("Update Account: "+str(number[0]))
#function_updateAccount(number[0])
account = function_getAccount(number[0], True)
print(account)
nextPlan = function_getPlanForDay(number[0], tomorrow.year, tomorrow.month, tomorrow.day)
if account["currentPlan"] == nextPlan:
print("Plan match, nothing to do")
else:
api = FunkAPI(account["mail"], account["password"])
if nextPlan == "1 GB":
api.order1GBPlan()
elif nextPlan == "unlimited":
api.orderUnlimitedPlan()
elif nextPlan == "Break":
api.startPause()
else:
print("SOMETHING GO WRONG")
cur.execute("INSERT INTO run_actions VALUES (%s, %s, %s) " % (today.year, today.month, today.day))
get_db().commit()
return "OK";
else:
return "Done for today"
#Routes
@app.route("/")
@requires_auth
def hello():
return render_template("base.html")
@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("/accounts")
@requires_auth
def gui_liste():
return render_template("list.html", 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", account=account, special_days=special_days, currentTime=int(time.time()))
@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_getPlanForDay(number, date.year, date.month, date.day)
dayList.append(day)
return render_template("list_days.html", 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)
# 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 = api.getCurrentPlan()["productServiceInfo"]["marketingInfo"]["name"]
name = personalData["firstName"]+" "+personalData["lastName"]
# 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,
plan
))
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()
if __name__ == '__main__':
app.run(debug=True,host='0.0.0.0')

205
templates/account.html Normal file
View file

@ -0,0 +1,205 @@
{% extends "base.html" %}
{% block content %}
<h3>Accounts {{ account["mail"] }}</h3>
<span id="info"></span>
<table class="table">
<tr>
<th>Owner</th>
<td>{{ account["owner"] }}</td>
</tr>
<tr>
<th>Mail</th>
<td>{{ account["mail"] }}</td>
</tr>
<tr>
<th>Number</th>
<td>{{ account["number"] }}</td>
</tr>
<tr>
<th>Current Plan</th>
<td>{{ account["currentPlan"] }}</td>
</tr>
<tr>
<th>Default Plan</th>
<td>{{ account["defaultPlan"] }}</td>
</tr>
<tr>
<th>Updatet min ago</th>
<td>
{{ ((currentTime - account["lastUpdate"]) /60)|round(1) }}
</td>
</tr>
<tr>
<th>Data Usage</th>
<td>{{ account["dataUsed"] }}%</td>
</tr>
</table>
<a href="#" class="btn btn-warning btn-sm updateNow">Update now</a>
<hr>
<h4>Update Default Plan</h4>
<p>The Default Plan will be set if no other Rule is enabled for the Day.</p>
<select class="form-control" id="defaultPlan">
<option value="1 GB" {% if account["defaultPlan"] == '1 GB' %}selected{% endif %}>1 GB</option>
<option value="unlimited" {% if account["defaultPlan"] == 'unlimited' %}selected{% endif %}>Unlimit</option>
<option value="Break" {% if account["defaultPlan"] == 'Break' %}selected{% endif %}>Break</option>
</select><br>
<input type="button" class="btn btn-warning" value="Save" id="changeDefaultPlan">
<hr>
<h4>Special Days</h4>
<p>Add some Special Days</p>
<b>From</b>
<form method="post" action="/account/{{ account['number'] }}/special">
<div class="row">
<div class="col-md-4">
<select name="from_day" class="form-control">
<option>01</option>
<option>02</option>
<option>03</option>
<option>04</option>
<option>05</option>
<option>06</option>
<option>07</option>
<option>08</option>
<option>09</option>
<option>11</option>
<option>12</option>
<option>13</option>
<option>14</option>
<option>15</option>
<option>16</option>
<option>17</option>
<option>18</option>
<option>19</option>
<option>21</option>
<option>22</option>
<option>23</option>
<option>24</option>
<option>25</option>
<option>26</option>
<option>27</option>
<option>28</option>
<option>29</option>
<option>30</option>
<option>31</option>
</select>
</div>
<div class="col-md-4">
<select name="from_month" class="form-control">
<option value="1">Januar</option>
<option value="2">Feburar</option>
<option value="3">März</option>
<option value="4">April</option>
<option value="5">Mai</option>
<option value="6">Juni</option>
<option value="7">Juli</option>
<option value="8">August</option>
<option value="9">September</option>
<option value="10">Okbotber</option>
<option value="11">November</option>
<option value="12">Dezember</option>
</select>
</div>
<div class="col-md-4">
<select name="from_year" class="form-control">
<option>2019</option>
<option>2020</option>
<option>2021</option>
</select>
</div>
</div>
<b>To</b>
<div class="row">
<div class="col-md-4">
<select name="to_day" class="form-control">
<option>01</option>
<option>02</option>
<option>03</option>
<option>04</option>
<option>05</option>
<option>06</option>
<option>07</option>
<option>08</option>
<option>09</option>
<option>11</option>
<option>12</option>
<option>13</option>
<option>14</option>
<option>15</option>
<option>16</option>
<option>17</option>
<option>18</option>
<option>19</option>
<option>21</option>
<option>22</option>
<option>23</option>
<option>24</option>
<option>25</option>
<option>26</option>
<option>27</option>
<option>28</option>
<option>29</option>
<option>30</option>
<option>31</option>
</select>
</div>
<div class="col-md-4">
<select name="to_month" class="form-control">
<option value="1">Januar</option>
<option value="2">Feburar</option>
<option value="3">März</option>
<option value="4">April</option>
<option value="5">Mai</option>
<option value="6">Juni</option>
<option value="7">Juli</option>
<option value="8">August</option>
<option value="9">September</option>
<option value="10">Okbotber</option>
<option value="11">November</option>
<option value="12">Dezember</option>
</select>
</div>
<div class="col-md-4">
<select name="to_year" class="form-control">
<option>2019</option>
<option>2020</option>
<option>2021</option>
</select>
</div>
</div>
<b>Plan</b>
<select name="plan" class="form-control">
<option value="1 GB">1 GB</option>
<option value="Unlimit">Unlimit</option>
<option value="Break">Break</option>
<option value="Default">Default (Delet all specal days in this Period)</option>
</select><br>
<input type="submit" class="btn btn-success" value="Save">
</form>
<hr>
<h4>Next Special Days</h4>
<table class="table">
{% for day in special_days %}
<tr>
<td> {{ day["day"] }}</td>
<td> {{ day["plan"] }}</td>
</tr>
{% endfor %}
</table>
<a href="/account/{{account["number"]}}/days" class="btn btn-sm btn-success">See next 2 Month</a>
<script>
$(".updateNow").click(function() {
$("#info").html("Update Account");
$.get("/api/account/{{account['number']}}/update", function() {
$("#info").html("Update Account done");
location.reload();
});
});
$("#changeDefaultPlan").click(function () {
$("#info").html("Update Default Plan");
$.post("/api/account/{{account['number']}}/changeDafault", {"plan": $("#defaultPlan").val()}, function() {
location.reload();
});
});
</script>
{% endblock %}

View file

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block content %}
<h3>Add Account</h3>
<form method="post" action="">
<label>E-Mail</label>
<input name="mail" class="form-control">
<label>Password</label>
<input name="password" class="form-control" type="password"><br>
<input type="submit" class="btn btn-success" value="Add Account">
</form>
{% endblock %}

35
templates/base.html Normal file
View file

@ -0,0 +1,35 @@
<head>
<title>FUNK Manager</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</head>
<body>
<div class="container" style="margin-top: 30px;">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/">Funk Manager</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="/accounts">Accounts</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/account/add">New Account</a>
</li>
</ul>
</div>
</nav>
<div class="row" style="margin-top:20px;">
<div class="col-md-12">
{% block content %}{% endblock %}
</div>
</div>
</div>
</body>

28
templates/list.html Normal file
View file

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block content %}
<h3>FUNK Accounts</h3>
<table class="table">
<tr>
<th>Number</th>
<th>Data Usage</th>
<th>Current Plan</th>
<th>Default Plan</th>
<th>Last Update</th>
<th>Actions</th>
</tr>
{% for account in accounts %}
<tr>
<td> +{{ account["number"] }}</td>
<td>
<div class="progress">
<div class="progress-bar" role="progressbar" style="width: {{ account["dataUsed"] }}%;" aria-valuenow="{{ account["dataUsed"] }}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
</td>
<td> {{ account["currentPlan"] }}</td>
<td> {{ account["defaultPlan"] }}</td>
<td> {{ ((currentTime - account["lastUpdate"]) /60)|round(1) }} min</td>
<td> <a href="/account/{{ account["number"] }}" class="btn btn-sm btn-warning">Edit</a> </td>
</tr>
{% endfor %}
</table>
{% endblock %}

16
templates/list_days.html Normal file
View file

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<h3>Days</h3>
<table class="table">
<tr>
<th>Day</th>
<th>Plan</th>
</tr>
{% for day in dayList %}
<tr>
<td>{{ day["date"] }}</td>
<td>{{ day["plan"] }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}