2023-10-16 21:58:50 +02:00
|
|
|
|
2023-10-18 12:55:20 +02:00
|
|
|
from flask import Flask,redirect,url_for,request,render_template,make_response
|
2023-10-17 18:25:38 +02:00
|
|
|
from datetime import datetime
|
2023-10-18 12:55:20 +02:00
|
|
|
from hashlib import sha256
|
|
|
|
from uuid import uuid4 as uuid
|
2023-10-17 18:25:38 +02:00
|
|
|
## Import db class from func.py and initialise it
|
|
|
|
from func import db
|
2023-10-19 19:39:54 +02:00
|
|
|
## import all config variables
|
|
|
|
from config import *
|
2023-10-17 18:25:38 +02:00
|
|
|
db=db()
|
2023-10-18 12:55:20 +02:00
|
|
|
db.startup()
|
2023-10-17 18:25:38 +02:00
|
|
|
|
2023-10-16 21:58:50 +02:00
|
|
|
app = Flask(__name__)
|
|
|
|
|
2023-10-20 15:27:25 +02:00
|
|
|
########## CUSTOM FILTERS
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
## converts a unix timestamp to a human-readable format
|
|
|
|
## OUTPUT: str="2023-11-02 12:33"
|
2023-10-17 18:25:38 +02:00
|
|
|
@app.template_filter('ctime')
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
def timectime(s:int):
|
2023-10-17 18:25:38 +02:00
|
|
|
return datetime.utcfromtimestamp(s).strftime('%Y-%m-%d %H:%M')
|
2023-10-18 17:24:59 +02:00
|
|
|
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
## converts a bytes to a human readable scale
|
|
|
|
## OUTPUT: str="XXX.XXYB"
|
2023-10-17 18:25:38 +02:00
|
|
|
@app.template_filter('spacer')
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
def convsize(s:int):
|
2023-10-17 18:25:38 +02:00
|
|
|
sizes=("B","KB","MB","GB","TB")
|
|
|
|
n=0
|
2023-10-19 13:25:59 +02:00
|
|
|
while s >= 1000:
|
2023-10-17 18:25:38 +02:00
|
|
|
n+=1
|
|
|
|
s=s/1000
|
|
|
|
return str("%.2f" % s)+sizes[n]
|
|
|
|
|
2023-10-19 18:24:36 +02:00
|
|
|
|
2023-10-20 15:27:25 +02:00
|
|
|
########## WEB FRONTEND
|
2023-10-16 21:58:50 +02:00
|
|
|
@app.route('/')
|
|
|
|
def homepage():
|
2023-10-18 17:24:59 +02:00
|
|
|
# try to get userdata, else logout state
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
logged_in,userdata=get_login_info(request.cookies.get('session-id'))
|
2023-10-17 18:25:38 +02:00
|
|
|
archives=db.get_n_archives()
|
2023-10-18 12:55:20 +02:00
|
|
|
return render_template("home.html", title="Homepage",userdata=userdata,login=logged_in,archives=archives)
|
|
|
|
|
|
|
|
@app.route('/user')
|
2023-10-20 16:50:05 +02:00
|
|
|
@app.route('/user/<int:page_userid>', methods=['GET','POST'])
|
|
|
|
def userpage(page_userid:int=0):
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
logged_in,userdata=get_login_info(request.cookies.get('session-id'))
|
2023-10-19 13:25:59 +02:00
|
|
|
if not logged_in:
|
|
|
|
return make_response(redirect('/'))
|
2023-10-20 16:50:05 +02:00
|
|
|
if page_userid == 0:
|
|
|
|
return make_response(redirect(f"/user/{userdata[0]}"))
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
res,page_userdata=db.get_user_info(page_userid)
|
|
|
|
if not res:
|
|
|
|
return errorpage(page_userdata) # page_userdata is error
|
2023-10-20 16:50:05 +02:00
|
|
|
|
|
|
|
# POST: Update display name or password
|
|
|
|
if request.method == 'POST':
|
|
|
|
match request.form['edit-type']:
|
|
|
|
case "password":
|
|
|
|
old_passhash=sha256(request.form['old-pass'].encode()).hexdigest()
|
|
|
|
if not old_passhash == db.get_passhash(userdata[1])[2]:
|
|
|
|
return errorpage("The old password does not match!")
|
|
|
|
new_password=request.form['new-pass']
|
|
|
|
conf_password=request.form['conf-pass']
|
|
|
|
if not new_password == conf_password:
|
|
|
|
return errorpage("The new passwords do not match!")
|
|
|
|
res,data=db.update_user_info(userdata[0],"PASSHASH",sha256(new_password.encode()).hexdigest().upper())
|
|
|
|
case "dname":
|
|
|
|
res,data=db.update_user_info(userdata[0],"DNAME",request.form['display-name'])
|
|
|
|
case _:
|
|
|
|
return make_response(redirect(f"/user/{page_userid}"))
|
|
|
|
if not res:
|
|
|
|
return errorpage("Something went wrong: " + data)
|
|
|
|
return make_response(redirect('/'))
|
|
|
|
|
|
|
|
# GET: return normal info page
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
return render_template("user.html", title="User Details",login=logged_in,userdata=page_userdata,login_userid=userdata[0],userid=page_userid)
|
2023-10-20 16:50:05 +02:00
|
|
|
|
|
|
|
@app.route('/user/<uname>')
|
|
|
|
def user_redirect(uname:str):
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
res,userdata=db.get_user_info(uname)
|
|
|
|
if not res:
|
|
|
|
return errorpage(userdata) # userdata is error
|
2023-10-20 16:50:05 +02:00
|
|
|
if not userdata:
|
|
|
|
return make_response(redirect(f"/user"))
|
|
|
|
return make_response(redirect(f"/user/{userdata[0]}"))
|
2023-10-19 13:25:59 +02:00
|
|
|
|
|
|
|
@app.route('/add', methods=['GET','POST'])
|
|
|
|
def addpage():
|
|
|
|
# try to get userdata, else yeet to the homepage
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
logged_in,userdata=get_login_info(request.cookies.get('session-id'))
|
2023-10-19 13:25:59 +02:00
|
|
|
if not logged_in:
|
2023-10-18 12:55:20 +02:00
|
|
|
return make_response(redirect('/'))
|
2023-10-19 13:25:59 +02:00
|
|
|
# POST: check and add archive, show confirmation/error message at the end
|
|
|
|
if request.method == 'POST':
|
|
|
|
postdict={}
|
|
|
|
# get and save all inputs, error if one doesn't exist or is wrong type
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
for i,itype in [("name",str),("hash",str),("category",int),("size",float),("size_multiplier",int)]:
|
2023-10-19 13:25:59 +02:00
|
|
|
try:
|
|
|
|
postdict[i]=itype(request.form[i])
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
if i == "size_multiplier":
|
|
|
|
postdict["size"]+=i
|
2023-10-19 13:25:59 +02:00
|
|
|
except Exception as e:
|
2023-10-19 19:14:18 +02:00
|
|
|
return errorpage("All fields need to be filled and don't play with their names!")
|
2023-10-19 13:25:59 +02:00
|
|
|
|
|
|
|
postdict["owner"]=userdata[0]
|
|
|
|
res,archid=db.add_archive(postdict)
|
|
|
|
if res:
|
|
|
|
return make_response(redirect(f"/view/{str(archid)}"))
|
|
|
|
else:
|
2023-10-19 19:14:18 +02:00
|
|
|
return errorpage(archid), 400
|
2023-10-19 13:25:59 +02:00
|
|
|
|
|
|
|
# GET: return normal page
|
|
|
|
htmlcatlist=get_category_selection(False)
|
|
|
|
return render_template("add.html", title="Add Archive",categories=htmlcatlist)
|
2023-10-18 12:55:20 +02:00
|
|
|
|
|
|
|
@app.route('/login', methods=["GET","POST"])
|
|
|
|
def loginpage():
|
|
|
|
# POST: Process login request
|
|
|
|
if request.method == 'POST':
|
|
|
|
username=request.form['username']
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
login_passhash=sha256(request.form['password'].encode()).hexdigest()
|
|
|
|
res,userid,db_passhash=db.get_passhash(username)
|
|
|
|
if not res:
|
|
|
|
return errorpage(userid)# userid is the error
|
2023-10-18 12:55:20 +02:00
|
|
|
# if passwords match, create session and return cookie
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
if login_passhash.upper() == db_passhash.upper():
|
2023-10-19 19:39:54 +02:00
|
|
|
lifetime=RAR_COOKIE_LIFETIME # lifetime of the sesskey in seconds
|
2023-10-18 12:55:20 +02:00
|
|
|
sesskey=str(uuid())
|
|
|
|
db.set_sesskey(sesskey,userid,lifetime)
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
resp=setcookie("session-id",sesskey,lifetime)
|
2023-10-18 12:55:20 +02:00
|
|
|
return resp
|
|
|
|
else:
|
2023-10-19 19:14:18 +02:00
|
|
|
return errorpage("You've entered the wrong password. This incident will be reported.")
|
2023-10-18 12:55:20 +02:00
|
|
|
# GET: Login form
|
|
|
|
else:
|
|
|
|
return render_template("login.html", title="Login")
|
|
|
|
|
2023-10-20 15:27:25 +02:00
|
|
|
@app.route('/logout')
|
|
|
|
def logout():
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
sesskey=request.cookies.get('session-id')
|
2023-10-20 15:27:25 +02:00
|
|
|
logged_in,userdata=get_login_info(sesskey)
|
|
|
|
if not logged_in:
|
|
|
|
return make_response(redirect('/login'))
|
|
|
|
db.logout_user(sesskey)
|
|
|
|
return make_response(redirect('/'))
|
|
|
|
|
2023-10-19 18:24:36 +02:00
|
|
|
@app.route('/view/<int:archid>')
|
|
|
|
def viewpage(archid:int):
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
logged_in,userdata=get_login_info(request.cookies.get('session-id'))
|
|
|
|
res,archive,category,labels=db.get_archive_info(archid)
|
|
|
|
if not res:
|
|
|
|
return errorpage(archive) # archive is error
|
2023-10-19 18:24:36 +02:00
|
|
|
return render_template("view.html", title="View Archive",userdata=userdata,login=logged_in,archive=archive,category=category,labels=labels)
|
|
|
|
|
2023-10-19 19:14:18 +02:00
|
|
|
@app.route('/delete/<int:archid>', methods=["GET","POST"])
|
|
|
|
def deletepage(archid:int):
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
logged_in,userdata=get_login_info(request.cookies.get('session-id'))
|
|
|
|
res,archive,category,labels=db.get_archive_info(archid)
|
|
|
|
if not res:
|
|
|
|
return errorpage(archive) # archive is error
|
2023-10-19 19:14:18 +02:00
|
|
|
if not logged_in or userdata[0] != archive[8]:
|
|
|
|
return make_response(redirect(f"/view/{archid}"))
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
# POST: check if input is correct and delete relevant data
|
2023-10-19 19:14:18 +02:00
|
|
|
if request.method == 'POST':
|
|
|
|
if not request.form['archname'] == archive[1]:
|
|
|
|
return errorpage("The name input doesn't match!")
|
|
|
|
db.delete_archive(archid)
|
|
|
|
return make_response(redirect('/'))
|
|
|
|
# GET: return normal deletion page
|
|
|
|
return render_template("delete.html", title="Delete Archive",userdata=userdata,login=logged_in,archive=archive)
|
|
|
|
|
2023-10-19 18:24:36 +02:00
|
|
|
@app.route('/labels/<int:archid>', methods=["GET","POST"])
|
|
|
|
def labeleditpage(archid:int):
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
logged_in,userdata=get_login_info(request.cookies.get('session-id'))
|
|
|
|
res,archive,category,labels=db.get_archive_info(archid)
|
|
|
|
if not res:
|
|
|
|
return errorpage(archive) # archive is error
|
2023-10-19 18:24:36 +02:00
|
|
|
label_dict=db.get_label_labeltypes(category[3])
|
|
|
|
if not logged_in or userdata[0] != archive[8]:
|
|
|
|
return make_response(redirect(f"/view/{archid}"))
|
|
|
|
|
|
|
|
# POST: parse everything and update labels
|
|
|
|
if request.method == 'POST':
|
|
|
|
on_labels=[]
|
|
|
|
for i in request.form:
|
|
|
|
on_labels.append(i)
|
|
|
|
res, data=db.update_labels(archid, on_labels)
|
|
|
|
if not res:
|
2023-10-19 19:14:18 +02:00
|
|
|
return errorpage(data)
|
2023-10-19 18:24:36 +02:00
|
|
|
return make_response(redirect(f"/view/{archid}"))
|
|
|
|
|
|
|
|
# GET: return normal labels page
|
|
|
|
labels_name_list=[]
|
|
|
|
for i in labels:
|
2023-10-20 15:27:25 +02:00
|
|
|
labels_name_list.append(i[1])
|
2023-10-19 18:24:36 +02:00
|
|
|
return render_template("labels.html", title="Edit Labels",userdata=userdata,login=logged_in,archive=archive,res_labels=label_dict,labels_names=labels_name_list)
|
|
|
|
|
2023-10-18 17:24:59 +02:00
|
|
|
@app.route('/search')
|
|
|
|
def searchpage():
|
|
|
|
# try to get userdata, else logout state
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
logged_in,userdata=get_login_info(request.cookies.get('session-id'))
|
2023-10-18 17:24:59 +02:00
|
|
|
|
|
|
|
# try to set all required variables, else defaults
|
|
|
|
try:
|
|
|
|
sorttype=request.args['sort']
|
|
|
|
except Exception as e:
|
|
|
|
sorttype="time"
|
|
|
|
try:
|
|
|
|
category=request.args['category']
|
2023-10-20 15:27:25 +02:00
|
|
|
label_dict=db.get_label_labeltypes(int(category))
|
2023-10-18 17:24:59 +02:00
|
|
|
except Exception as e:
|
|
|
|
category=0
|
2023-10-20 15:27:25 +02:00
|
|
|
label_dict={}
|
2023-10-18 17:24:59 +02:00
|
|
|
try:
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
keywords="".join(request.args['q']).split(" ")
|
2023-10-18 17:24:59 +02:00
|
|
|
except Exception as e:
|
|
|
|
keywords=[]
|
|
|
|
try:
|
|
|
|
count=request.args['count']
|
|
|
|
except Exception as e:
|
|
|
|
count=20
|
2023-10-20 15:27:25 +02:00
|
|
|
labels=[]
|
|
|
|
try:
|
|
|
|
for i in request.args:
|
|
|
|
try:
|
|
|
|
int(i)
|
|
|
|
labels.append(i)
|
|
|
|
except Exception as e:
|
|
|
|
continue
|
|
|
|
except Exception as e:
|
|
|
|
labels=[]
|
|
|
|
archives=db.get_n_archives(sorttype,category,keywords,count,labels)
|
2023-10-18 17:24:59 +02:00
|
|
|
|
2023-10-19 13:25:59 +02:00
|
|
|
htmlcatlist=get_category_selection()
|
2023-10-18 17:24:59 +02:00
|
|
|
|
2023-10-20 15:27:25 +02:00
|
|
|
return render_template("search.html", title="Advanced Search",categories=htmlcatlist,userdata=userdata,login=logged_in,archives=archives,res_labels=label_dict,labels=labels)
|
2023-10-18 17:24:59 +02:00
|
|
|
|
2023-10-20 15:27:25 +02:00
|
|
|
########## FUNCTIONS
|
2023-10-18 17:24:59 +02:00
|
|
|
|
2023-10-19 19:14:18 +02:00
|
|
|
def errorpage(message):
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
return "<h2>ERROR: " + str(message) + "</h2>Go back and try again", 400
|
2023-10-19 19:14:18 +02:00
|
|
|
|
2023-10-18 17:24:59 +02:00
|
|
|
## Checks if given sesskey is valid and returns user data
|
2023-10-19 13:25:59 +02:00
|
|
|
## OUTPUT: (if sesskey valid) logged_in:bool=True, userdata:tuple
|
|
|
|
## (if sesskey invalid) logged_in:bool=False, userdata:tuple=()
|
2023-10-18 17:24:59 +02:00
|
|
|
def get_login_info(sesskey:str):
|
2023-10-19 13:25:59 +02:00
|
|
|
if not sesskey:
|
|
|
|
return False,()
|
2023-10-18 17:24:59 +02:00
|
|
|
logged_in,userid=db.check_sesskey(sesskey)
|
|
|
|
if logged_in:
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
res,userdata=db.get_user_info(userid)
|
|
|
|
if not res:
|
|
|
|
return errorpage(userdata) # userdata is error
|
2023-10-18 17:24:59 +02:00
|
|
|
else:
|
|
|
|
userdata=()
|
|
|
|
return logged_in,userdata
|
|
|
|
|
2023-10-18 12:55:20 +02:00
|
|
|
def setcookie(name:str,value:str,lifetime:int=10000):
|
|
|
|
resp = make_response(redirect('/'))
|
|
|
|
resp.set_cookie(name, value, max_age=lifetime)
|
|
|
|
return resp
|
2023-10-16 21:58:50 +02:00
|
|
|
|
2023-10-19 13:25:59 +02:00
|
|
|
## Gets all categories and returns them (with or without parents)
|
|
|
|
## OUTPUT: […,(ID:int,NAME:str),…]
|
|
|
|
def get_category_selection(include_parents:bool=True):
|
2023-10-19 18:24:36 +02:00
|
|
|
catlist=db.get_categories()
|
2023-10-19 13:25:59 +02:00
|
|
|
htmlcatlist=[]
|
|
|
|
# parse all categories and sort them into list
|
|
|
|
for cat in catlist:
|
|
|
|
if not cat[2]:
|
|
|
|
if include_parents:
|
|
|
|
htmlcatlist.append((cat[0],cat[1]))
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
parentname=cat[1]
|
2023-10-19 13:25:59 +02:00
|
|
|
parentid=cat[0]
|
|
|
|
for i in catlist:
|
|
|
|
if i[2] == parentid:
|
Many fixes and improvements
Fixes: #20,#22,#23,#24,#25,#26,#27,#28,#29,#30,#31,#32,#33,#34,#35,#36,#37,#38,#39,#40,#41,#42,#43,#46,#48
2023-11-02 15:20:03 +01:00
|
|
|
htmlcatlist.append((i[0],f"{parentname}/{i[1]}"))
|
2023-10-19 13:25:59 +02:00
|
|
|
return htmlcatlist
|
|
|
|
|
2023-10-18 17:24:59 +02:00
|
|
|
## API CALLS (NO THANKS)
|
2023-10-16 21:58:50 +02:00
|
|
|
|
|
|
|
# main driver function
|
|
|
|
if __name__ == '__main__':
|
|
|
|
# run app if executed directly
|
|
|
|
app.run()
|