heartranked

git clone https://git.tarina.org/heartranked
Log | Files | Refs | README

server.py (84954B)


      1 #!/usr/bin/python3
      2 # -*- coding: utf-8 -*-
      3 #heartranked is FREEDOM SOFTWARE by King Robin Johannes
      4 #don't worry, information want's to be free. nobody can stop it. Bless.
      5 
      6 import time, datetime, os, sys
      7 
      8 file_dir = os.path.dirname(__file__)
      9 sys.path.append(file_dir)
     10 
     11 import json
     12 import requests
     13 import subprocess
     14 import web
     15 import hashlib
     16 import random
     17 import settings
     18 import markdown
     19 import re
     20 import unicodedata
     21 import urllib
     22 from pathlib import Path
     23 from mutagen.flac import FLAC
     24 from mutagen.easyid3 import EasyID3
     25 from mutagen.oggvorbis import OggVorbis
     26 from PIL import Image
     27 from PIL import ImageSequence
     28 import settings
     29 
     30 urls = (
     31     '/?','heartranked',
     32     '/trust', 'trust',
     33     "/stats?", "stats",
     34     "/login?", "login",
     35     "/logout", "logout",
     36     "/invites?", "invites",
     37     "/users","users",
     38     "/like?","like",
     39     "/imageapi?","imageapi",
     40     "/u/(.*)?", "user",
     41     "/forgotpass?", "forgotpass",
     42     "/register?", "register",
     43     "/tuning?", "tuning",
     44     '/editor?', 'editor',
     45     '/save', 'savepost',
     46     '/upload', 'upload',
     47     '/rendered', 'rendered',
     48     '/uploads?', 'uploads',
     49     '/pull?', 'pull',
     50     '/config', 'config')
     51 
     52 #Load from settings
     53 webmaster = settings.webmaster
     54 baseurl = settings.baseurl
     55 sitename = settings.sitename
     56 siteslogan = settings.siteslogan
     57 siteurl = baseurl
     58 postadmin = settings.postadmin
     59 postadmin_signature = settings.postadmin_signature
     60 heart=settings.heart
     61 hearted=settings.hearted
     62 zipandship=settings.zipandship
     63 #load msg
     64 postadmin_msg_new=settings.postadmin_msg_new
     65 postadmin_msg_new_long=settings.postadmin_msg_new_long
     66 welcome=settings.welcome
     67 welcome_long=settings.welcome_long
     68 basedir = os.path.dirname(os.path.realpath(__file__))+'/'
     69 templatedir = basedir + 'html/'
     70 staticdir = basedir + 'static/'
     71 web.config.debug = False
     72 app = web.application(urls, globals())
     73 store = web.session.DiskStore(basedir + 'sessions')
     74 render = web.template.render(templatedir, base="base")
     75 renderop = web.template.render(templatedir, base="op")
     76 rendersplash = web.template.render(templatedir, base="splash")
     77 session = web.session.Session(app,store,initializer={'login':0, 'privilege':0, 'bag':[], 'sessionkey':'empty','postid':'','backurl':'','user':'','search':'', 'bildsida':'', 'feedbase':'', 'timebase':'', 'usrfeed':''})
     78 
     79 allowedchar = 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'
     80 
     81 # --- Parameters (often called N, r, p) ---
     82 # N (n): CPU/Memory cost factor. Must be a power of 2 (e.g., 2**14 = 16384).
     83 # r: Block size factor (typically 8).
     84 # p: Parallelization factor (typically 1).
     85 # klen: Desired key length (e.g., 32 for a 256-bit key).
     86 N_COST = 16384  # Adjust this for desired security/speed tradeoff
     87 R_BLOCK_SIZE = 8
     88 P_PARALLELIZATION = 1
     89 KEY_LENGTH = 32
     90 
     91 def runfirst():
     92     os.makedirs(basedir+'p/posts',exist_ok=True)
     93     os.makedirs(basedir+'p/zipped',exist_ok=True)
     94     os.makedirs(basedir+'p/comborank',exist_ok=True)
     95     os.makedirs(basedir+'p/heartrank',exist_ok=True)
     96     os.makedirs(basedir+'p/deleted',exist_ok=True)
     97     os.makedirs(basedir+'u/',exist_ok=True)
     98     os.makedirs(basedir+'r/',exist_ok=True)
     99     os.makedirs(basedir+'r/visitors',exist_ok=True)
    100     os.makedirs(basedir+'r/invites',exist_ok=True)
    101     os.makedirs(basedir+'r/trusted',exist_ok=True)
    102     os.makedirs(basedir+'r/users',exist_ok=True)
    103     os.makedirs(basedir+'r/stopflood',exist_ok=True)
    104     os.makedirs(basedir+'r/stopresetpass',exist_ok=True)
    105 
    106 runfirst()
    107 
    108 def hash_password(password: str) -> bytes:
    109     # 1. Generate a random, unique salt for each password
    110     salt = os.urandom(16) 
    111     # 2. Derive the key (hash)
    112     # The 'password' must be bytes, so we use .encode('utf-8')
    113     key = hashlib.scrypt(
    114         password.encode('utf-8'),
    115         salt=salt,
    116         n=N_COST,
    117         r=R_BLOCK_SIZE,
    118         p=P_PARALLELIZATION,
    119         dklen=KEY_LENGTH
    120     ) 
    121     # 3. Store the salt AND the key (hash) together
    122     return salt + key
    123 
    124 def verify_password(stored_hash_with_salt: bytes, provided_password: str) -> bool:
    125     # 1. Separate the salt and the stored key
    126     salt = stored_hash_with_salt[:16] # Assuming 16 bytes for the salt
    127     stored_key = stored_hash_with_salt[16:]
    128     # 2. Re-derive the key from the provided password using the stored salt and parameters
    129     # **Crucially, use the exact same n, r, p, and dklen parameters!**
    130     try:
    131         derived_key = hashlib.scrypt(
    132             provided_password.encode('utf-8'),
    133             salt=salt,
    134             n=N_COST,
    135             r=R_BLOCK_SIZE,
    136             p=P_PARALLELIZATION,
    137             dklen=KEY_LENGTH
    138         )
    139         # 3. Compare the newly derived key with the stored key
    140         # Use a constant-time comparison (like `hmac.compare_digest` if available,
    141         # but Python's standard byte comparison is often okay here too)
    142         return derived_key == stored_key
    143     except ValueError:
    144         # This can happen if parameters like dklen are wrong during verification
    145         return False
    146 
    147 def logged():
    148     if session.login > 0:
    149         return True
    150     else:
    151         return False
    152 
    153 class createpost:
    154     def __init__(self, name, value):
    155         self.name = name
    156         self.value = value
    157 
    158 def savetext(thename, description):
    159     with open(basedir+thename, "w") as f:
    160         f.write(description)
    161 
    162 def loadtext(thename):
    163     if os.path.exists(basedir+thename):
    164         with open(basedir+thename, 'r') as f:
    165             description = f.read()
    166         return description
    167     return ''
    168 
    169 def savejson(name, thedict):
    170     #full path to filename
    171     #save to next line make an ID automatically, you have to check the save to know how to load it.
    172     #loadfile check if record exist, first in line is the record hash and update if it exists
    173     #make users in a folder as files first is username username will always be user record
    174     #hearts will be files as usernames with timestamp in a hearts folder in post folder. be stored and checked in u/hearts and post/hearts
    175     # be sure to add home domain setting to username
    176     #make save list a dict use same names
    177     print(thedict)
    178     print('this to be saved')
    179     thename=basedir+name
    180     if os.path.exists(thename):
    181         with open(thename, 'r') as f:
    182             p=json.load(f)
    183         print(p)
    184         print('this to be saved to')
    185         p.update(thedict)
    186         with open(thename, "w") as f:
    187             json.dump(p,f)
    188     else:
    189         with open(thename, "w") as f:
    190             #f.write(str(i) + ',')
    191             print('saving json')
    192             json.dump(thedict,f)
    193 
    194 def loadjson(name):
    195     settings = ''
    196     thename=basedir+name
    197     if os.path.exists(thename):
    198         with open(thename, 'r') as f:
    199             settings = json.load(f)
    200             #for key, i in settings.items():
    201             #    createpost(key,i)
    202     return settings
    203 
    204 def deletepost(thefile):
    205     os.system('rm '+thefile)
    206 
    207 def adduser(name, password, mail):
    208     originalname=name
    209     name=safe_filename(name[:12])
    210     password_hashed=hash_password(password).hex()
    211     tot = len(os.listdir(basedir+'r/users/'))
    212     #print(password_hashed)
    213     print('users alltsomallt: ' + str(tot))
    214     if tot > 1:
    215         adminlevel=3
    216     else:
    217         adminlevel=5
    218     thedict={'name':name, 'displayname':originalname, 'password':password_hashed,'mail':mail,'adminlevel':adminlevel}
    219     savejson('r/users/'+name, thedict)
    220     #savetext('r/user/'+name,password_hashed)
    221     print("new user added")
    222     return
    223 
    224 def updateuser(displayname, password, mail):
    225     password_hashed=hash_password(password).hex()
    226     tot = len(os.listdir(basedir+'r/users/'))
    227     thedict={'displayname':displayname, 'password':password_hashed,'mail':mail}
    228     savejson('r/users/'+session.user, thedict)
    229     print("user info updated")
    230     return
    231 
    232 def safe_filename(name: str, max_length: int = 100, replacement: str = "-") -> str:
    233     """
    234     Convert a filename into a web-safe version.
    235     """
    236     if not name:
    237         return "file"
    238     #Normalize unicode (é → e, etc.)
    239     name = unicodedata.normalize('NFKD', name)
    240     name = name.encode('ascii', 'ignore').decode('ascii')
    241     #Replace spaces and common separators with the replacement char
    242     name = re.sub(r'[\s_]+', replacement, name)
    243     #Remove any character that is not alphanumeric, hyphen, underscore, or dot
    244     name = re.sub(r'[^a-zA-Z0-9.\-_]', '', name)
    245     #Replace multiple replacement chars with single one
    246     name = re.sub(re.escape(replacement) + r'+', replacement, name)
    247     #Remove leading/trailing replacement chars and dots
    248     name = name.strip(replacement + '.')
    249     #Prevent empty or hidden files
    250     if not name or name.startswith('.'):
    251         name = "file" + name
    252     #Enforce max length (leave room for extension)
    253     if len(name) > max_length:
    254         name = name[:max_length]
    255     return name.lower()
    256 
    257 def adminlevel(user):
    258     #level = db.query("SELECT adminlevel FROM rymdadmin WHERE name='"+user+"';")[0]
    259     level=loadjson('r/users/'+user)
    260     #1 session logout, web.py bug
    261     #2 rights to see pics and comment
    262     #3 rights to upoload
    263     #5 superadmin
    264     session.login = int(level['adminlevel'])
    265     return
    266 
    267 def stopresetpass(mail):
    268     t = None
    269     if os.path.exists(basedir+'r/stopresetpass/'+mail) == True:
    270         t=loadjson('r/stopresetpass/'+mail)
    271     else:
    272         thedict={'timeadded':time.time()}
    273         savejson('r/stopresetpass/'+mail, thedict)
    274         return 
    275     thedict={'timeadded':time.time()}
    276     savejson('r/stopresetpass/'+mail, thedict)
    277     latest = time.time() - t['timeadded']
    278     print(latest)
    279     if latest < 600:
    280         print('mail is in password reset spam filter')
    281         return True
    282     else:
    283         return False
    284 
    285 def stopflood(ip,referer):
    286     t = 0
    287     if os.path.exists(basedir+'r/stopflood/'+ip) == True:
    288         t=loadjson('r/stopflood/'+ip)
    289     else:
    290         thedict={'timeadded':time.time()}
    291         savejson('r/stopflood/'+ip, thedict)
    292         return
    293     thedict={'timeadded':time.time()}
    294     savejson('r/stopflood/'+ip, thedict)
    295     try:
    296         latest = time.time() - t
    297         print(latest)
    298     except:
    299         latest = 10
    300         pass
    301     if latest < 1:
    302         print('flooding recognized!')
    303         return True
    304     else:
    305         return False
    306 
    307 def getinvitation(secretinvitation):
    308     os.makedirs(basedir+'r/invites/'+session.user+'/',exist_ok=True)
    309     invite=loadjson('r/invites/'+session.user+'/'+secretinvitation)
    310     print(invite)
    311     if invite['secretinvitekey'] == secretinvitation:
    312         return True
    313     return False
    314 
    315 class trust():
    316     form = web.form.Form(
    317     web.form.Textbox('servername', web.form.notnull, description="server:"),
    318     web.form.Textbox('port', description="port:"),
    319     web.form.Textbox('user', web.form.notnull, description="user:"),
    320     web.form.Password('password', web.form.notnull, description="passcode:"),
    321     web.form.Button('Trust'))
    322     def GET(self):
    323         os.makedirs(basedir+'r/trusted/'+session.user+'/',exist_ok=True)
    324         i = web.input(remove=None)
    325         if i.remove != None:
    326             os.system('rm '+basedir+'r/trusted/'+session.user+'/'+i.remove)
    327             return web.seeother('/trust')
    328         trusted=os.listdir(basedir+'r/trusted/'+session.user+'/')
    329         trustedlist=[]
    330         for t in trusted:
    331             trusted=loadjson('r/trusted/'+session.user+'/'+t)
    332             trustedlist.append(trusted)
    333         trustform = self.form()
    334         return render.trust(trustform, trustedlist)
    335     def POST(self):
    336         i = web.input(port='')
    337         referer = web.ctx.env.get('HTTP_REFERER',baseurl)
    338         ip = web.ctx['ip']
    339         stopflood(ip, referer)
    340         loginform = self.form()
    341         i = web.input()
    342         #password_hashed=hash_password(i.password).hex()
    343         thedict={'servername':i.servername, 'port':i.port, 'user':i.user, 'password':i.password}
    344         savejson('r/trusted/'+session.user+'/'+i.servername,thedict)
    345         return web.seeother('/trust')
    346 
    347 class login():
    348     form = web.form.Form(
    349     web.form.Textbox('user', web.form.notnull, description="your registered mail account:"),
    350     web.form.Password('password', web.form.notnull, description="and your passcode please:"),
    351     web.form.Button('Login'))
    352     def GET(self):
    353         users=os.listdir(basedir+'r/users/')
    354         if len(users) == 0:
    355             result = subprocess.run(['whoami'], capture_output=True, text=True)
    356             adduser('op', 'blessyou', result.stdout.rstrip()+'@localhost')
    357         visitorlog()
    358         fejl = ''
    359         resetpasslink = False
    360         i = web.input(error=None)
    361         if i.error == 'fejl':
    362             fejl = 'wrong passcode!'
    363             resetpasslink = True
    364         if i.error == 'tom':
    365             fejl = 'didnt work'
    366         if session.login < 3:
    367             loginform = self.form()
    368             return render.login(loginform, fejl, resetpasslink)
    369         if session.login == 3:
    370             return web.seeother('/')
    371         if session.login == 5:
    372             raise web.seeother('/')
    373     def POST(self):
    374         visitorlog()
    375         referer = web.ctx.env.get('HTTP_REFERER',baseurl)
    376         ip = web.ctx['ip']
    377         stopflood(ip, referer)
    378         loginform = self.form()
    379         i = web.input()
    380         if i.user == '' or i.password == '':
    381             raise web.seeother('/login?error=tom')
    382         rymdadmins = []
    383         users = os.listdir(basedir+'r/users/')
    384         for r in users:
    385             admin=loadjson('r/users/'+r)
    386             rymdadmins.append(admin)
    387         #if not rymdadmins:
    388         #    raise web.seeother('/register')
    389         for p in rymdadmins:
    390             if p['name'].lower() == i['user'].lower() or p['mail'].lower() == i['user'].lower():
    391                 passcode=bytes.fromhex(p['password'])
    392                 if verify_password(passcode,i.password):
    393                     session.user = p['name']
    394                     adminlevel(p['name'])
    395                     if session.login == 5:
    396                         print('ACCESS!')
    397                         raise web.seeother('/')
    398                     if session.backurl != '':
    399                         backurl = session.backurl
    400                         session.backurl = ''
    401                         raise web.seeother(backurl)
    402                     else:
    403                         raise web.seeother('/')
    404         return web.seeother('/login?error=fejl')
    405 
    406 class register():
    407     form = web.form.Form(
    408     web.form.Textbox('invite', description="invitation code (do not edit):"),
    409     web.form.Textbox('user', description="name:"),
    410     web.form.Password('password', description="passcode:"),
    411     web.form.Textbox('mail', description="mail:"),
    412     web.form.Button('JOIN'))
    413     def GET(self):
    414         visitorlog()
    415         registerform = self.form()
    416         w = web.input(invite=None)
    417         formfail = ''
    418         n = ''
    419         m = ''
    420         if getinvitation(w.invite):
    421             try:
    422                 if w.fail == 'namn':
    423                     formfail = 'hey, need a name. If ya don lik ya real name imagination buddy'
    424                 if w.namn:
    425                     n = w.namn
    426                 if w.epost:
    427                     m = w.epost
    428                 elif w.epost == '':
    429                     formfail = 'we need an email, if u loose your passcode for example...'
    430                 if w.fail == 'notmail':
    431                     formfail = 'uhm, this is not an email'
    432                 elif w.fail == 'nametaken':
    433                     formfail = 'Name already taken'
    434                 elif w.fail == 'mailtaken':
    435                     formfail = 'You already got a account on this email. Try reset your passcode'
    436                 elif w.fail == 'kortlosen':
    437                     formfail = 'Too shoort passcode. Min 5 char.'
    438             except:
    439                 pass
    440             totusers = len(os.listdir(basedir+'r/users/'))
    441             registerform.fill(user=urllib.parse.unquote_plus(n), mail=urllib.parse.unquote_plus(m), invite=w.invite)
    442             return render.register(registerform, formfail, totusers)
    443         else:
    444             return web.seeother('/oopsie')
    445     def POST(self):
    446         visitorlog()
    447         registerform = self.form()
    448         i = web.input(invite=None)
    449         if getinvitation(i.invite):
    450             r = '&namn=' + i.user + '&epost=' + i.mail
    451             urllib.parse.quote_plus(r)
    452             if i.user == '':
    453                 raise web.seeother('/register?invite='+i.invite+'&fail=namn'+r)
    454             if '@' not in i.mail:
    455                 raise web.seeother('/register?invite='+i.invite+'&fail=notmail'+r)
    456             if len(i.password) < 5:
    457                 raise web.seeother('/register?invite='+i.invite+'&fail=kortlosen'+r)
    458             #rymdadmins = db.select('rymdadmin', what='name, mail')
    459             rymdadmins = []
    460             users = os.listdir(basedir+'r/users/')
    461             for r in users:
    462                 admin=loadjson('r/users/'+r)
    463                 rymdadmins.append(admin)
    464             for p in rymdadmins:
    465                 if p['name'].lower() == i.user.lower():
    466                     raise web.seeother('/register?invite='+i.invite+'&fail=nametaken' +r)
    467                 if p['mail'].lower() == i.mail.lower():
    468                     raise web.seeother('/register?invite='+i.invite+'&fail=mailtaken' +r)
    469             adduser(i.user, i.password, i.mail.lower())
    470             #Send mail (change messages in settings.py)
    471             sendmail(postadmin, postadmin_msg_new, postadmin_msg_new_long+i.user+' '+i.mail)
    472             #Send mail to new user
    473             sendmail(i.mail, welcome, welcome_long+i.user)
    474             #session.login = 3
    475             #session.user = safe_filename(i.user)
    476             #add user to matrix
    477             #os.system("register_new_matrix_user -u "+i.user+" -p "+i.password+" --no-admin -c /etc/matrix-synapse/homeserver.yaml") 
    478             #db.update('invites', where='secretinvitation="'+i.invite+'"', accepted=formattime(datetime.datetime.now()))
    479             return web.seeother('/login')
    480         else:
    481             raise web.seeother('/oopsie')
    482 
    483 class welcome():
    484     def GET(self):
    485         if session.login > 2:
    486             backurl = ''
    487             if session.backurl != '':
    488                 backurl = session.backurl
    489                 session.backurl = ''
    490             return render.ny(session.user, backurl)
    491 
    492 class like:
    493     def POST(self):
    494         if session.user != '':
    495             i = web.input(unlike=None, like=None, hate=None, unhate=None, user=None, imghash=None)
    496             user = i.user
    497             postid = i.imghash
    498             mail=''
    499             displayname=''
    500             rymdadmins=[]
    501             users = os.listdir(basedir+'r/users/')
    502             for r in users:
    503                 admin=loadjson('r/users/'+r)
    504                 rymdadmins.append(admin)
    505             for r in rymdadmins:
    506                 if r['name']==session.user:
    507                     if 'displayname' in r:
    508                         displayname=r['displayname']
    509                     if 'mail' in r:
    510                         mail=r['mail']
    511             #l = db.query("SELECT * FROM likes WHERE bild='"+postid+"' AND user='"+session.user+"';")
    512             os.makedirs(basedir+'p/posts/'+postid+'/hearts/',exist_ok=True)
    513             os.makedirs(basedir+'p/heartrank/',exist_ok=True)
    514             os.makedirs(basedir+'u/'+session.user+'/posts/'+postid+'/hearts/',exist_ok=True)
    515             try:
    516                 l = loadjson('p/posts/'+postid+'/hearts/'+session.user)
    517             except:
    518                 l={}
    519             print(session.user)
    520             if l:
    521                 user_likes = True
    522             else:
    523                 user_likes = False
    524             if user_likes == False:
    525                 #db.insert('likes', user=session.user, bild=postid, datum=formattime(datetime.datetime.now()))
    526                 try:
    527                     l=len(os.listdir(basedir+'p/posts/'+postid+'/hearts/'))
    528                 except:
    529                     l=0
    530                 l=l+1
    531                 thedict={'hearts':l,'name':session.user,'mail':mail,'displayname':displayname,'timeadded':formattime(datetime.datetime.now())}
    532                 savejson('p/posts/'+postid+'/meta',thedict)
    533                 os.system('rm '+basedir+'p/heartrank/'+postid+'-'+str(int(l-1)).zfill(16))
    534                 os.system('cp '+basedir+'p/posts/'+postid+'/meta '+basedir+'p/heartrank/'+postid+'-'+str(int(l)).zfill(16))
    535                 savejson('p/posts/'+postid+'/hearts/'+session.user, thedict)
    536                 savejson('u/'+session.user+'/posts/'+postid+'/hearts/'+session.user, thedict)
    537                 user_likes = True
    538             elif user_likes == True:
    539                 try:
    540                     l=len(os.listdir(basedir+'p/posts/'+postid+'/hearts/'))
    541                 except:
    542                     l=0
    543                 if l > 0:
    544                     l=l-1
    545                     thedict={'hearts':l,'name':session.user,'mail':mail,'displayname':displayname,'timeadded':formattime(datetime.datetime.now())}
    546                     savejson('p/posts/'+postid+'/meta',thedict)
    547                     deletepost(basedir+'u/'+session.user+'/posts/'+postid+'/hearts/'+session.user)
    548                     deletepost(basedir+'p/posts/'+postid+'/hearts/'+session.user)
    549                     os.system('rm '+basedir+'p/heartrank/'+postid+'-'+str(int(l+1)).zfill(16))
    550                     os.system('cp '+basedir+'p/posts/'+postid+'/meta '+basedir+'p/heartrank/'+postid+'-'+str(int(l)).zfill(16))
    551                 user_likes = False
    552             likes = len(os.listdir(basedir+'p/posts/'+postid+'/hearts/'))
    553             # Example: Update like count in your database
    554             # This is a placeholder; replace with your database logic
    555             # Return JSON response
    556             web.header('Content-Type', 'application/json')
    557             return json.dumps({'likes': likes, 'user_likes': user_likes ,'heart':heart,'hearted':hearted})
    558 
    559 class user():
    560     def GET(self, user):
    561         data = web.input(soundname=None, onair=None, public=None, showuploads=None)
    562         if user == session.user:
    563             if data.public and data.soundname:
    564                 public=data.public
    565                 thedict={'timeadded':formattime(datetime.datetime.now())}
    566                 savejson('p/posts/'+postid+'/meta')
    567             elif data.showuploads=='yes':
    568                 uploads = []
    569                 uploads = get_files_by_time('public_html/u/' + user + '/images/web/',newest_first=True)
    570                 return render.showuploads(uploads,user,allowedchar, random)
    571             elif data.onair and data.soundname:
    572                 onair={"onair":data.onair}
    573                 savejson('p/posts/'+postid+'/meta')
    574             #soundname='aurora_ruderalis-greatful_bread'
    575             #filetype='flac'
    576             #postid = hashlib.md5(str(random.getrandbits(256)).encode('utf-8')).hexdigest()
    577             usersounds=loadjson('r/users/'+user)
    578             #sounds=os.listdir(basedir+'p/posts/')
    579             creditsounds = []
    580             #for i in sounds:
    581             #    creditsounds.append(loadjson('p/posts/'+i))
    582             #for i in creditsound:
    583             #    try:
    584             #        credits=i['musicians'].split(',')
    585             #    except:
    586             #        credits=''
    587             #    for u in credits:
    588             #        if u.strip().lower() == user.strip().lower():
    589             #            creditsounds.append(i.title)
    590             return render.user(usersounds,creditsounds,user,datetime,str,int)
    591         return web.seeother('/login')
    592 
    593 class invites():
    594     form = web.form.Form(
    595     web.form.Textbox('mail', description="epost:"), 
    596     web.form.Button('Skicka'))
    597     def GET(self):
    598         if session.login > 2:
    599             os.makedirs(basedir+'r/invites/'+session.user+'/',exist_ok=True)
    600             invites=[]
    601             v = get_files_by_time('r/invites/'+session.user,newest_first=True) 
    602             if v:
    603                 for i in v:
    604                     invite=loadjson('r/invites/'+session.user+'/'+i)
    605                     invites.append(invite)
    606             tuningform = self.form()
    607             w = web.input(epost=None, render=None)
    608             formfail = ''
    609             if w.epost == '':
    610                 formfail = formfail + 'you have to put your email in'
    611             if w.render == 'yes':
    612                 secretinvitekey = hashlib.md5(str(random.getrandbits(256)).encode('utf-8')).hexdigest()
    613                 thedict={"secretinvitekey":secretinvitekey,"timeadded":formattime(datetime.datetime.now()),"creator":session.user, "accepted":''}
    614                 savejson('r/invites/'+session.user+'/'+secretinvitekey, thedict)
    615                 raise web.seeother('/invites')
    616             return render.invites(tuningform, formfail, session.user, invites)
    617     def POST(self):
    618         if session.login > 2:
    619             user = loadjson('r/users/'+session.user)
    620             tuningform = self.form()
    621             i = web.input()
    622             if i.mail == '':
    623                 raise web.seeother('/invites?fail=nomail')
    624             if '@' not in i.mail:
    625                 raise web.seeother('/tuning?fail=notmail') 
    626             secretinvitekey = hashlib.md5(str(random.getrandbits(256)).encode('utf-8')).hexdigest()
    627             thedict={"secretinvitekey":secretinvitekey,"timeadded":formattime(datetime.datetime.now()),"creator":session.user, "accepted":''}
    628             savejson('r/invites/'+session.user+'/'+secretinvitekey, thedict)
    629             msg = "YO! You are the One! " + user.name + " is your Morpheous. Follow this rabbit https://robinbackman.com/register?invite="+secretinvitekey 
    630             sendmail(i.mail, 'Invitation to HEART RANKED!', msg)
    631         return web.seeother('/')
    632 
    633 class tuning():
    634     form = web.form.Form(
    635     web.form.Textbox('user', description="synligt namn:"),
    636     web.form.Password('password', description="lösenord:"),
    637     web.form.Password('newpassword', description="nytt lösen (ifall du vill byta):"),
    638     web.form.Password('newpassword2', description="nytt lösen igen:"),
    639     web.form.Textbox('mail', description="epost:"), 
    640     web.form.Button('Spara'))
    641     def GET(self):
    642         if session.login > 2:
    643             user = loadjson('r/users/'+session.user)
    644             tuningform = self.form()
    645             w = web.input(namn=None,epost=None,fail=None,upd=None)
    646             formfail = ''
    647             if w.fail == 'wrongpass':
    648                 formfail = formfail + 'wrong passcode'
    649             if w.fail == 'nopass':
    650                 formfail = formfail + 'you have to write passcode'
    651             if w.namn == '':
    652                 formfail = formfail + 'whats your displayname'
    653             if w.epost == '':
    654                 formfail = formfail + 'write your email please'
    655             elif w.fail == 'notmail':
    656                 formfail = formfail + 'email please'
    657             if w.fail == 'nametaken':
    658                 formfail = formfail + 'name is taken! choose another'
    659             if w.fail == 'mailtaken':
    660                 formfail = formfail + 'mail address taken'
    661             if w.fail == 'kortlosen':
    662                 formfail = formfail + '5 characters minimum'
    663             if w.fail == 'newpass':
    664                 formfail = formfail + 'new passcode doesnt match'
    665             if w.upd == 'yes':
    666                 formfail = 'Yes, your account has been tuned in thanks!'
    667             tuningform.fill(user=user['displayname'], mail=user['mail'])
    668             return render.tuning(tuningform, formfail, user['name'])
    669         else:
    670             return web.seeother('/register')
    671     def POST(self):
    672         if session.login > 2:
    673             tuningform = self.form()
    674             i = web.input()
    675             if i.password == '':
    676                 raise web.seeother('/tuning?fail=nopass')
    677             rymdadmins = []
    678             users = os.listdir(basedir+'r/users/')
    679             for r in users:
    680                 admin=loadjson('r/users/'+r)
    681                 rymdadmins.append(admin)
    682             for p in rymdadmins:
    683                 print(p)
    684                 if p['name'] == session.user:
    685                     passcode=bytes.fromhex(p['password'])
    686                     if verify_password(passcode,i.password):
    687                         #check if display name taken
    688                         for a in rymdadmins:
    689                             if i.user in a['displayname'] and a['name'] != session.user:
    690                                 raise web.seeother('/tuning?fail=nametaken')
    691                             if i.mail in a['mail'] and i.mail != p['mail']:
    692                                 raise web.seeother('/tuning?fail=mailtaken')
    693                         if i.newpassword != '':
    694                             if i.newpassword != i.newpassword2:
    695                                 raise web.seeother('/tuning?fail=newpass')
    696                             if len(i.newpassword) < 5:
    697                                 raise web.seeother('/tuning?fail=kortlosen')
    698                             else:
    699                                 #update with password change
    700                                 updateuser(i.user,i.newpassword,i.mail)
    701                                 return web.seeother('/tuning?upd=yes')
    702                         if '@' not in i.mail:
    703                             raise web.seeother('/tuning?fail=notmail')
    704                         #update without passwordchange
    705                         thedict={'displayname':i.user,'mail':i.mail.lower()}
    706                         savejson('r/users/'+session.user, thedict)
    707                         return web.seeother('/tuning?upd=yes')
    708                     else:
    709                         raise web.seeother('/tuning?fail=wrongpass')
    710 
    711 class forgotpass():
    712     form = web.form.Form(
    713             web.form.Textbox('mail', web.form.notnull, description="email:"),
    714             web.form.Button('Send me new passcode'))
    715     def GET(self):
    716         fejl = ''
    717         i = web.input(error=None)
    718         if i.error == 'fejl':
    719             fejl = 'no email like that sorry!'
    720         elif i.error == 'done':
    721             fejl = 'your passcode is updated and sent to your mail'
    722         elif i.error == 'nej':
    723             fejl = 'nope, dont worky'
    724         elif i.error == 'stopresetpass':
    725             fejl = 'Already sent passcode to mail'
    726         if session.login < 3:
    727             loginform = self.form()
    728             return render.forgotpass(loginform, fejl)
    729         if session.login == 3:
    730             return web.seeother('/s')
    731         if session.login == 5:
    732             raise web.seeother('/s')
    733     def POST(self):
    734         referer = web.ctx.env.get('HTTP_REFERER',baseurl)
    735         ip = web.ctx['ip']
    736         stopflood(ip, referer)
    737         sendpassform = self.form()
    738         if not sendpassform.validates():
    739             raise web.seeother('/forgotpass?error=fejl')
    740         else:
    741             i = web.input()
    742             if '@' not in i.mail:
    743                 raise web.seeother('/forgotpass?error=fejl')
    744             rymdadmins = []
    745             users = os.listdir(basedir+'r/users/')
    746             for r in users:
    747                 admin=loadjson('r/users/'+r)
    748                 rymdadmins.append(admin)
    749             for p in rymdadmins:
    750                 if p['mail'].lower() == i.mail.lower():
    751                     passfilter = stopresetpass(i.mail.lower())
    752                     if passfilter == True:
    753                         raise web.seeother('/forgotpass?error=stopresetpass')
    754                     unencrypted_password = ('%06x' % random.randrange(16**6))
    755                     password = unencrypted_password.encode("utf-8")
    756                     password_hashed=hash_password(password).hex()
    757                     thedict={'password':password_hashed}
    758                     savejson('r/users/'+session.user, thedict)
    759                     print("lösenordet uppdaterat!")
    760                     msg = "Your new passcode is: " + unencrypted_password
    761                     sendmail(p.mail, 'Heart Ranked Passcode', msg)
    762                     raise web.seeother('/forgotpass?error=done')
    763             raise web.seeother('/forgotpass?error=fejl')
    764 
    765 def sendmail(email, subject, msg):
    766     #Send mail
    767     try:
    768         echomsg = subprocess.Popen(('echo', msg+'\n'+postadmin_signature), stdout=subprocess.PIPE)
    769         sendmsg = subprocess.check_output(('mail', '-r', postadmin, '-s', subject, email), stdin=echomsg.stdout)
    770         echomsg.wait()
    771     except:
    772         print('no mail server found')
    773         #subprocess.call(['echo', msg, '|', 'mail', '-r', postadmin,'-s', subject, email])
    774 
    775 def get_new_frames(gif, scale):
    776     new_frames = []
    777     actual_frames = gif.n_frames
    778     for frame in range(actual_frames):
    779         gif.seek(frame)
    780         new_frame = Image.new('RGBA', gif.size)
    781         new_frame.paste(gif)
    782         new_frame.thumbnail(scale, Image.Resampling.LANCZOS)
    783         new_frames.append(new_frame)
    784     return new_frames
    785 
    786 def save_new_gif(new_frames, old_gif_information, new_path):
    787     new_frames[0].save(new_path,
    788                        save_all = True,
    789                        append_images = new_frames[1:],
    790                        duration = old_gif_information['duration'],
    791                        loop = old_gif_information['loop'],
    792                        background = old_gif_information['background'],
    793                        extension = old_gif_information['extension'] ,
    794                        transparency = old_gif_information['transparency'])
    795 
    796 def resize_gif(input_path, output_path, max_size):
    797     input_image = Image.open(input_path)
    798     frames = list(_thumbnail_frames(input_image,max_size))
    799     output_image = frames[0]
    800     output_image.save(
    801         output_path,
    802         save_all=True,
    803         append_images=frames[1:],
    804         disposal=input_image.disposal_method,
    805         **input_image.info,
    806     )
    807 
    808 def _thumbnail_frames(image,max_size):
    809     for frame in ImageSequence.Iterator(image):
    810         new_frame = frame.copy()
    811         new_frame.thumbnail(max_size, Image.Resampling.LANCZOS)
    812         yield new_frame
    813 
    814 def scale_gif(path, scale, new_path=None):
    815     gif = Image.open(path)
    816     if not new_path:
    817         new_path = path
    818     old_gif_information = {
    819         'loop': bool(gif.info.get('loop', 1)),
    820         'duration': gif.info.get('duration', 40),
    821         'background': gif.info.get('background', 223),
    822         'extension': gif.info.get('extension', (b'NETSCAPE2.0')),
    823         'transparency': gif.info.get('transparency', 223)
    824     }
    825     new_frames = get_new_frames(gif, scale)
    826     save_new_gif(new_frames, old_gif_information, new_path)
    827 
    828 
    829 def getdisplayname(user):
    830     try:
    831         displayname = loadjson('r/users/'+session.user)
    832         displayname = displayname.displayname
    833     except:
    834         displayname = user
    835     return displayname
    836 
    837 #-------------Get files and sort em by date modified---------------
    838 
    839 def get_dirs_by_time(path, reverse=False):
    840     """Return names of immediate subdirectories, sorted by modification time."""
    841     path=basedir+path
    842     with os.scandir(path) as it:
    843         return sorted(
    844             (e.name for e in it if e.is_dir(follow_symlinks=False)),
    845             key=lambda name: os.stat(os.path.join(path, name)).st_mtime,
    846             reverse=reverse,
    847         )
    848 
    849 def get_files_by_time(directory: str = ".", newest_first: bool = True):
    850     path = Path(basedir+directory) 
    851     #Get all files (exclude directories and hidden files if you want)
    852     files = [f for f in path.iterdir() if f.is_file()]
    853     #Sort by modification time
    854     sorted_files = sorted(
    855         files,
    856         key=lambda f: f.stat().st_mtime,   # last modified timestamp
    857         reverse=newest_first
    858     ) 
    859     # Return just the file names (as strings)
    860     return [f.name for f in sorted_files]
    861 
    862 def getfiles(filmfolder):
    863     #get a list of films, in order of settings.p file last modified
    864     films_sorted = []
    865     films = next(os.walk(filmfolder))[1]
    866     for i in films:
    867         uploaded = os.listdir(filmfolder + i + '/')
    868         for f in uploaded:
    869             if os.path.isfile(filmfolder + i + '/'+f) == True:
    870                 #DUUUUUUDE HERE CHECK IF OLDER THAN
    871                 lastupdate = os.path.getmtime(filmfolder + i + '/' + f)
    872                 films_sorted.append((i,f,lastupdate))
    873         else:
    874             films_sorted.append((i,f,0))
    875     films_sorted = sorted(films_sorted, key=lambda tup: tup[2], reverse=True)
    876     return films_sorted
    877 
    878 def callsubprocess(cmd):
    879     subprocess.call(cmd.split())
    880 
    881 def visitorlog():
    882     ip = web.ctx['ip']
    883     referer = web.ctx.env.get('HTTP_REFERER', 'none')
    884     environ = web.ctx.env.get('HTTP_USER_AGENT', 'dunno')
    885     last = get_files_by_time('r/visitors/',newest_first=True)
    886     stopflood(ip, referer)
    887     country='local loco'
    888     countrycode='fi'
    889     if last:
    890         lastip=loadjson('r/visitors/'+last[0])
    891     else:
    892         lastip=''
    893         try:
    894             country = ''
    895             country = os.popen('geoiplookup '+ip).read()
    896             print(country)
    897             if country != '':
    898                 countrycode = country.split(':')[1].split(',')[0].lower().strip()
    899                 country = country.split(':')[1].split(',')[1].strip()
    900             else:
    901                 country='none'
    902         except:
    903             country='local loco'
    904             countrycode='fi'
    905         thedict={'ip':ip,'referer':referer,'environ':environ,'country':country,'countrycode':countrycode,'time':formattime(datetime.datetime.now())}
    906         savejson('r/visitors/'+ip,thedict)
    907         print("added to visitor log")
    908     if lastip != ip:
    909         try:
    910             country = ''
    911             country = os.popen('geoiplookup '+ip).read()
    912             #print(soundtype)
    913             countrycode = country.split(':')[1].split(',')[0].lower().strip()
    914             country = country.split(':')[1].split(',')[1].strip()
    915             #db.insert('visitors', ip=ip, referer=referer, environ=environ, country=country,  countrycode=countrycode, time=formattime(datetime.datetime.now()))
    916             thedict={'ip':ip,'referer':referer,'environ':environ,'country':country,'countrycode':countrycode,'time':formattime(datetime.datetime.now())}
    917             savejson('r/visitors/'+ip,thedict)
    918         except:
    919             pass
    920         print("added to visitor log")
    921     return
    922 
    923 def getvisitors():
    924     visitors=[]
    925     v = get_files_by_time('r/visitors/',newest_first=True) 
    926     if v:
    927         for i in v:
    928             visit=loadjson('r/visitors/'+i)
    929             visitors.append(visit)
    930             print(visit)
    931     total=len(os.listdir(basedir+'r/visitors/'))
    932     unique=[]
    933     for i in visitors:
    934         for p in visitors:
    935             if i['ip'] == p['ip']:
    936                 unique.append(i)
    937     uniquevisits=len(unique)
    938     return visitors, total, uniquevisits
    939 
    940 def getvisits():
    941     visitors=[]
    942     #v=os.listdir(basedir+'r/visitors/')
    943     v = get_files_by_time('r/visitors/',newest_first=True) 
    944     for i in v:
    945         visit=loadjson('r/visitors/'+i)
    946         visitors.append(visit)
    947     total=len(os.listdir(basedir+'r/visitors/'))
    948     unique=[]
    949     for i in visitors:
    950         for p in visitors:
    951             if i['ip'] == p['ip']:
    952                 unique.append(i)
    953     uniquevisits=len(unique)
    954     countrylist=[]
    955     for i in visitors:
    956         if i['countrycode'] not in countrylist:
    957             countrylist.append(i['countrycode'])
    958     return countrylist, total, uniquevisits
    959 
    960 class stats:
    961     def GET(self):
    962         p = web.input(logfilter=None)
    963         visitors, total, unique = getvisitors()
    964         return rendersplash.stats(visitors, total, unique, p.logfilter)
    965 
    966 class logout:
    967     def GET(self):
    968         session.login = 0
    969         session.user = None
    970         raise web.seeother('/')
    971 
    972 def getlikes(postid, user):
    973     user_likes = False
    974     #l = db.query("SELECT Count(*) AS likes FROM likes WHERE bild='"+postid+"';")[0]
    975     os.makedirs(basedir+'p/posts/'+postid+'/hearts/',exist_ok=True)
    976     try:
    977         l=len(os.listdir(basedir+'p/posts/'+postid+'/hearts/'))
    978         print(os.listdir(basedir+'p/posts/'+postid+'/hearts/'))
    979     except:
    980         l=0
    981     #db.update('published', where='postid="'+postid+'"', hearts=l.likes)
    982     if user != None:
    983         #m = db.query("SELECT * FROM likes WHERE bild='"+postid+"' AND user='"+user+"';")
    984         m=loadjson('/p/posts/'+postid+'/hearts/'+user)
    985         if m:
    986             user_likes = True
    987         else:
    988             user_likes = False
    989     if l >= 0:
    990         if user_likes: 
    991             likes = hearted+" " + str(l)
    992         else: 
    993             if l > 0:
    994                 likes = heart+" " + str(l)
    995             else:
    996                 likes = heart
    997         return likes
    998 
    999 def postexist(postid):
   1000     try:
   1001         #l = db.select('published', where="postid='"+postid+"'")[0]
   1002         l=loadjson('/p/posts/'+postid+'/meta')
   1003     except:
   1004         return False
   1005     try:
   1006         if l['postid'] != None:
   1007             return True
   1008         else:
   1009             return False
   1010     except:
   1011         return False
   1012     return False
   1013 
   1014 def getcombines(postid):
   1015     #l = db.query("SELECT Count(*) AS combines FROM published WHERE combine='"+postid+"';")[0]
   1016     try:
   1017         l=len(os.listdir(basedir+'p/posts/'+postid+'/combos/'))
   1018     except:
   1019         l=0
   1020     print(postid)
   1021     #m = db.query("SELECT * FROM likes WHERE bild='"+postid+"' AND user='"+user+"';")
   1022     #db.update('published', where='postid="'+postid+'"', combines=0)
   1023     if int(l) > 0:
   1024         return "⚭ " + str(l)
   1025     else:
   1026         return ''
   1027 
   1028 def pushcombines(postid):
   1029     #l = db.query("SELECT Count(*) AS combines FROM published WHERE postid='"+postid+"';")[0]
   1030     try:
   1031         l=len(os.listdir(basedir+'p/posts/'+postid+'/combos/'))
   1032     except:
   1033         l=0
   1034     #m = db.query("SELECT * FROM likes WHERE bild='"+postid+"' AND user='"+user+"';")
   1035     #db.update('published', where='postid="'+postid+'"', combines=l.combines)
   1036     #thedict={'combines':l}
   1037     #savejson('/p/posts/'+postid+'/meta',thedict)
   1038     if l >= 0:
   1039             return "⚭ " + str(l)
   1040     else:
   1041         return ''
   1042 
   1043 def formattime(timeadded):
   1044     return timeadded.strftime("%Y-%m-%d %H:%M:%S")
   1045 
   1046 def sort_by_name_then_time(path):
   1047     sortedposts=[]
   1048     heartrank=os.listdir(basedir+path)
   1049     for h in heartrank:
   1050         postid=h.split('-')[0]
   1051         postrank=h.split('-')[1]
   1052         sortedposts.append((postrank,postid))
   1053     heartrank=sorted(sortedposts, reverse=True)
   1054     return heartrank
   1055 
   1056 def rankrender():
   1057     posts = os.listdir(basedir+'p/posts')
   1058     os.system('rm -r '+basedir+'p/heartrank')
   1059     os.system('rm -r '+basedir+'p/comborank')
   1060     os.makedirs(basedir+'p/comborank',exist_ok=True)
   1061     os.makedirs(basedir+'p/heartrank',exist_ok=True)
   1062     for postid in posts:
   1063         try:
   1064             l=len(os.listdir(basedir+'p/posts/'+postid+'/hearts/'))
   1065         except:
   1066             l=0
   1067         #db.update('published', where='postid="'+postid+'"', hearts=l.likes)
   1068         thedict={'hearts':l}
   1069         savejson('p/posts/'+postid+'/meta',thedict)
   1070         os.system('cp '+basedir+'p/posts/'+postid+'/meta '+basedir+'p/heartrank/'+postid+'-'+str(int(l)).zfill(16))
   1071         try:
   1072             l=len(os.listdir(basedir+'p/posts/'+postid+'/combos/'))
   1073         except:
   1074             l=0
   1075         #db.update('published', where='postid="'+postid+'"', hearts=l.likes)
   1076         thedict={'combos':l}
   1077         savejson('p/posts/'+postid+'/meta',thedict)
   1078         os.system('cp '+basedir+'p/posts/'+postid+'/meta '+basedir+'p/comborank/'+postid+'-'+str(int(l)).zfill(16))
   1079 
   1080 rankrender()
   1081 
   1082 def getfeed():
   1083     timebase=session.timebase
   1084     feedbase=session.feedbase
   1085     usr=session.usrfeed
   1086     print('fuuuuuu')
   1087     print(usr)
   1088     if feedbase == '':
   1089         feedbase = 'time'
   1090     if timebase == '':
   1091         timebase = 'week'
   1092     now = datetime.datetime.now()
   1093     goodies=[]
   1094     #HEARTS
   1095     #SAVE HEARTRANKING EVERY MINUTE CHECK BOTH USER LIKES AND POST LIKES IF IT CHECKS OUT GOOD IT NOT WRITE ERROR AT LEAST (A BACKEND PROGRAM, RUNS EVERY MINUTE AND COUNTS LIKES AND WRITES THE HEARTRANKING FOR TODAY. HEARTRANKING STAYES SAVED FOREVER IN FOLDERS BY DAYS. IT IS A FOLDER WITH NUMBERS. starting with 0000000000000001 pointing to postid. simple. effective.
   1096     #backend program will also sync posts and likes to trustees
   1097     if feedbase == "heart" and timebase == "today":
   1098         one_day_before = now - datetime.timedelta(days=1)
   1099         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY hearts DESC LIMIT 1000;")
   1100         #posts=os.listdir('/p/posts/')
   1101         #make function get_files_by_time newest_first and by today week month year
   1102         posts = sort_by_name_then_time('p/heartrank/')
   1103         for p in posts:
   1104             #check modtime here day
   1105             p=p[1]
   1106             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1107             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1108                 l=loadjson('p/posts/'+p+'/meta')
   1109                 if usr=='':
   1110                     goodies.append(l)
   1111                 if usr==l['creator']:
   1112                     goodies.append(l)
   1113     elif feedbase == "heart" and timebase == "week":
   1114         one_day_before = now - datetime.timedelta(weeks=1)
   1115         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY hearts DESC LIMIT 1000;")
   1116         posts = sort_by_name_then_time('p/heartrank/')
   1117         for p in posts:
   1118             #check modtime here day
   1119             p=p[1]
   1120             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1121             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1122                 l=loadjson('p/posts/'+p+'/meta')
   1123                 if usr=='':
   1124                     goodies.append(l)
   1125                 if usr==l['creator']:
   1126                     goodies.append(l)
   1127     elif feedbase == "heart" and timebase == "month":
   1128         one_day_before = now - datetime.timedelta(weeks=4)
   1129         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY hearts DESC LIMIT 1000;")
   1130         posts = sort_by_name_then_time('p/heartrank/')
   1131         for p in posts:
   1132             #check modtime here day
   1133             p=p[1]
   1134             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1135             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1136                 l=loadjson('p/posts/'+p+'/meta')
   1137                 if usr=='':
   1138                     goodies.append(l)
   1139                 if usr==l['creator']:
   1140                     goodies.append(l)
   1141     elif feedbase == "heart" and timebase == "year":
   1142         one_day_before = now - datetime.timedelta(weeks=54)
   1143         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY hearts DESC LIMIT 1000;")
   1144         posts = sort_by_name_then_time('p/heartrank/')
   1145         for p in posts:
   1146             #check modtime here day
   1147             p=p[1]
   1148             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1149             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1150                 l=loadjson('p/posts/'+p+'/meta')
   1151                 if usr=='':
   1152                     goodies.append(l)
   1153                 if usr==l['creator']:
   1154                     goodies.append(l)
   1155     elif feedbase == "heart" and timebase == "" or feedbase == "heart" and timebase == "all":
   1156         #goodies = db.query("SELECT * FROM published ORDER BY hearts DESC LIMIT 1000;")
   1157         posts = sort_by_name_then_time('/p/heartrank/')
   1158         for p in posts:
   1159             p=p[1]
   1160             l=loadjson('p/posts/'+p+'/meta')
   1161             if usr=='':
   1162                 goodies.append(l)
   1163             if usr==l['creator']:
   1164                 goodies.append(l)
   1165     #TIME
   1166     elif feedbase == "time" and timebase == "today":
   1167         one_day_before = now - datetime.timedelta(days=1)
   1168         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY ID DESC LIMIT 1000;")
   1169         posts = get_dirs_by_time('p/posts/', reverse=True)
   1170         print(posts)
   1171         for p in posts:
   1172             #check modtime here day
   1173             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1174             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1175                 l=loadjson('p/posts/'+p+'/meta')
   1176                 if usr=='':
   1177                     goodies.append(l)
   1178                 if usr==l['creator']:
   1179                     goodies.append(l)
   1180     elif feedbase == "time" and timebase == "week":
   1181         one_day_before = now - datetime.timedelta(weeks=1)
   1182         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY ID DESC LIMIT 1000;")
   1183         posts = get_dirs_by_time('p/posts/', reverse=True)
   1184         for p in posts:
   1185             #check modtime here day
   1186             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1187             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1188                 l=loadjson('p/posts/'+p+'/meta')
   1189                 if usr=='':
   1190                     goodies.append(l)
   1191                 if usr==l['creator']:
   1192                     goodies.append(l)
   1193     elif feedbase == "time" and timebase == "month":
   1194         one_day_before = now - datetime.timedelta(weeks=4)
   1195         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY ID DESC LIMIT 1000;")
   1196         posts = get_dirs_by_time('p/posts/', reverse=True)
   1197         for p in posts:
   1198             #check modtime here day
   1199             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1200             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1201                 l=loadjson('p/posts/'+p+'/meta')
   1202                 if usr=='':
   1203                     goodies.append(l)
   1204                 if usr==l['creator']:
   1205                     goodies.append(l)
   1206     elif feedbase == "time" and timebase == "year":
   1207         one_day_before = now - datetime.timedelta(weeks=54)
   1208         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY ID DESC LIMIT 1000;")
   1209         posts = get_dirs_by_time('p/posts/', reverse=True)
   1210         for p in posts:
   1211             #check modtime here day
   1212             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1213             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1214                 l=loadjson('p/posts/'+p+'/meta')
   1215                 if usr=='':
   1216                     goodies.append(l)
   1217                 if usr==l['creator']:
   1218                     goodies.append(l)
   1219     elif feedbase == "time" and timebase == "" or  feedbase == "time" and timebase == "all":
   1220         #goodies = db.query("SELECT * FROM published ORDER BY ID DESC LIMIT 1000;")
   1221         posts = get_dirs_by_time('p/posts/', reverse=True)
   1222         for p in posts:
   1223             l=loadjson('p/posts/'+p+'/meta')
   1224             if usr=='':
   1225                 goodies.append(l)
   1226             if usr==l['creator']:
   1227                 goodies.append(l)
   1228 
   1229     #COMBO
   1230     elif feedbase == "combo" and timebase == "today":
   1231         one_day_before = now - datetime.timedelta(days=1)
   1232         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY combines DESC LIMIT 1000;")
   1233         posts = sort_by_name_then_time('p/comborank/')
   1234         for p in posts:
   1235             #check modtime here day
   1236             p=p[1]
   1237             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1238             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1239                 l=loadjson('p/posts/'+p+'/meta')
   1240                 if usr=='':
   1241                     goodies.append(l)
   1242                 if usr==l['creator']:
   1243                     goodies.append(l)
   1244     elif feedbase == "combo" and timebase == "week":
   1245         one_day_before = now - datetime.timedelta(weeks=1)
   1246         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY combines DESC LIMIT 1000;")
   1247         posts = sort_by_name_then_time('p/comborank/')
   1248         for p in posts:
   1249             #check modtime here day
   1250             p=p[1]
   1251             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1252             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1253                 l=loadjson('p/posts/'+p+'/meta')
   1254                 if usr=='':
   1255                     goodies.append(l)
   1256                 if usr==l['creator']:
   1257                     goodies.append(l)
   1258     elif feedbase == "combo" and timebase == "month":
   1259         one_day_before = now - datetime.timedelta(weeks=4)
   1260         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY combines DESC LIMIT 1000;")
   1261         posts = sort_by_name_then_time('p/comborank/')
   1262         for p in posts:
   1263             #check modtime here day
   1264             p=p[1]
   1265             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1266             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1267                 l=loadjson('p/posts/'+p+'/meta')
   1268                 if usr=='':
   1269                     goodies.append(l)
   1270                 if usr==l['creator']:
   1271                     goodies.append(l)
   1272     elif feedbase == "combo" and timebase == "year":
   1273         one_day_before = now - datetime.timedelta(weeks=54)
   1274         #goodies = db.query("SELECT * FROM published WHERE timeadded BETWEEN '"+one_day_before+"' AND '"+now+"' ORDER BY combines DESC LIMIT 1000;")
   1275         posts = sort_by_name_then_time('p/comborank/')
   1276         for p in posts:
   1277             #check modtime here day
   1278             p=p[1]
   1279             lastupdate = os.path.getmtime(basedir+'p/posts/'+p+'/meta')
   1280             if datetime.datetime.fromtimestamp(lastupdate) > one_day_before:
   1281                 l=loadjson('p/posts/'+p+'/meta')
   1282                 if usr=='':
   1283                     goodies.append(l)
   1284                 if usr==l['creator']:
   1285                     goodies.append(l)
   1286     elif feedbase == "combo" and timebase == "" or  feedbase == "combo" and timebase == "all":
   1287         #goodies = db.query("SELECT * FROM published ORDER BY combines DESC LIMIT 1000;")
   1288         posts = sort_by_name_then_time('p/comborank/')
   1289         for p in posts:
   1290             #check modtime here day
   1291             p=p[1]
   1292             l=loadjson('p/posts/'+p+'/meta')
   1293             if usr=='':
   1294                 goodies.append(l)
   1295             if usr==l['creator']:
   1296                 goodies.append(l)
   1297     elif feedbase == "Idontevenknow":
   1298         #goodies = db.query("SELECT * FROM published ORDER BY combines DESC LIMIT 1000;")
   1299         print('write your own custom feed here')
   1300     else:
   1301         #goodies = db.query("SELECT * FROM published ORDER BY ID DESC LIMIT 1000;")
   1302         posts = os.listdir('p/posts/')
   1303         for p in posts:
   1304             l=loadjson('p/posts/'+p+'/meta')
   1305             if usr=='':
   1306                 goodies.append(l)
   1307             if usr==l['creator']:
   1308                 goodies.append(l)
   1309     return goodies
   1310 
   1311 def getcombofeed(show):
   1312     timebase=session.timebase
   1313     feedbase=session.feedbase
   1314     if feedbase == "heart":
   1315         #comboposts = db.query("SELECT * FROM published WHERE combine='"+show+"' ORDER BY hearts DESC LIMIT 1000;")
   1316         posts = sort_by_name_then_time('p/heartrank/')
   1317         comboposts=[]
   1318         for p in posts: 
   1319             p=p[1]
   1320             l=loadjson('p/posts/'+p+'/meta')
   1321             if 'combine' in l:
   1322                 if l['combine'] == show:
   1323                     comboposts.append(l)
   1324     elif feedbase == "combo":
   1325         #comboposts = db.query("SELECT * FROM published WHERE combine='"+show+"' ORDER BY combines DESC LIMIT 1000;")
   1326         posts = sort_by_name_then_time('p/comborank/')
   1327         comboposts=[]
   1328         for p in posts:
   1329             p=p[1]
   1330             l=loadjson('p/posts/'+p+'/meta')
   1331             if 'combine' in l:
   1332                 if l['combine'] == show:
   1333                     comboposts.append(l)
   1334     else:
   1335         #comboposts = db.query("SELECT * FROM published WHERE combine='"+show+"' ORDER BY ID DESC LIMIT 1000;")
   1336         posts = get_dirs_by_time('p/posts/',reverse=True)
   1337         comboposts=[]
   1338         for p in posts:
   1339             l=loadjson('p/posts/'+p+'/meta')
   1340             if 'combine' in l:
   1341                 if l['combine'] == show:
   1342                     comboposts.append(l)
   1343     return comboposts
   1344 
   1345 def userimage(user):
   1346     usrimg = ''
   1347     i = staticdir+'users/'+user+'/images/thumb/'+user
   1348     print(i)
   1349     if os.path.isfile(i+'.jpeg'):
   1350         usrimg='static/users/'+user+'/images/thumb/'+user+'.jpeg'
   1351     elif os.path.isfile(i+'.jpg'):
   1352         usrimg='static/users/'+user+'/images/thumb/'+user+'.jpg'
   1353     elif os.path.isfile(i+'.png'):
   1354         usrimg='static/users/'+user+'/images/thumb/'+user+'.png'
   1355     elif os.path.isfile(i+'.gif'):
   1356         usrimg='static/users/'+user+'/images/thumb/'+user+'.gif'
   1357     if usrimg != '':
   1358         imghtml='<img class="usrimg" src="'+usrimg+'">'
   1359         return imghtml
   1360     else:
   1361         return 
   1362 
   1363 class heartranked:
   1364     form = web.form.Form(web.form.Textbox('search', web.form.notnull, description="or search"))
   1365     def GET(self):
   1366         siterendered=formattime(datetime.datetime.now())
   1367         visitorlog()
   1368         visitors, total, unique = getvisits()
   1369         print(visitors)
   1370         print(str(total))
   1371         print(str(unique))
   1372         searchform = self.form()
   1373         bildpersida = 1000
   1374         session.search = ''
   1375         session.bildsida = 0
   1376         i = web.input(publised=None, public=None, show=None, remove=None, edit=None, feedbase=None, timebase=None, usrfeed=None)
   1377         #search
   1378         try:
   1379             #bilder_totalt = db.query("SELECT COUNT(*) AS sound FROM published")[0]
   1380             bilder_totalt=os.listdir(basedir+'p/posts/')
   1381             tot = len(bilder_totalt)
   1382             print('bilder alltsomallt: ' + str(tot))
   1383         except:
   1384             print("inga bilder")
   1385             tot = 0
   1386         #print('session search: ' + session.search)
   1387         try:
   1388             if i.search == '':
   1389                 session.search = ''
   1390                 session.bildsida = 0
   1391             elif i.search != "":
   1392                 session.search = urllib.parse.unquote_plus(i.search)
   1393                 session.bildsida = 0
   1394         except:
   1395             pass
   1396         if session.search != '':
   1397             search_result = []
   1398             tot = 0
   1399             try:
   1400                 #search_result.append(db.query("SELECT * FROM published WHERE creator LIKE '%"+session.search+"%' ORDER BY ID DESC LIMIT 1000;"))
   1401                 #tot = db.query("SELECT Count(*) AS sound FROM published WHERE creator LIKE '%"+session.search+"%';")[0]
   1402                 #b1 = tot.sound
   1403                 for i in bilder_totalt:
   1404                     searchthis=loadjson('p/posts/'+i+'/meta')
   1405                     for p in dir(searchthis):
   1406                         if session.search in p:
   1407                             search_result.append(searchthis)
   1408                             tot=tot+1
   1409             except:
   1410                 pass
   1411             try:
   1412                 print(search_result)
   1413                 print('sökta bilder: ' + str(tot))
   1414             except:
   1415                 pass
   1416         try:
   1417             if i.page == "next":
   1418                 if session.bildsida < tot:
   1419                     session.bildsida += bildpersida
   1420             if i.page == "back":
   1421                 if session.bildsida > bildpersida:
   1422                     session.bildsida -= bildpersida
   1423                 else:
   1424                     session.bildsida = 0
   1425         except:
   1426             pass
   1427         limit = session.bildsida + bildpersida
   1428         offset = session.bildsida
   1429         #EOF search
   1430         print(session.bildsida)
   1431         bilder=[]
   1432         if session.search == '':
   1433             #bilder = db.query("SELECT * FROM published ORDER BY id DESC LIMIT " + str(limit) + " OFFSET " + str(offset))
   1434             posts=os.listdir(basedir+'p/posts/')
   1435             for p in posts:
   1436                 this=loadjson('p/posts/'+p+'/meta')
   1437                 bilder.append(this)
   1438             #print(bilder)
   1439         else:
   1440             bilder = search_result
   1441         if i.feedbase == None:
   1442             feedbase = ''
   1443         else:
   1444             feedbase = i.feedbase
   1445             session.feedbase = feedbase
   1446         if i.timebase == None:
   1447             timebase = ''
   1448         else:
   1449             timebase = i.timebase
   1450             session.timebase = timebase
   1451         if i.timebase == None:
   1452             timebase = ''
   1453         else:
   1454             timebase = i.timebase
   1455             session.timebase = timebase
   1456         if i.usrfeed == None:
   1457             usrfeed = ''
   1458         else:
   1459             usrfeed = i.usrfeed
   1460             session.usrfeed = usrfeed
   1461         if session.user=='':
   1462             free_hash_for_user = hashlib.md5(str(random.getrandbits(256)).encode('utf-8')).hexdigest()[:4]
   1463             #session.user = 'heart_'+free_hash_for_user
   1464             session.user = None
   1465         if i.edit != None:
   1466             session.postid=i.edit
   1467             raise web.seeother('/editor?public=yes') 
   1468         if i.remove != None:
   1469             try:
   1470                 #user = db.select('published', where="postid='"+i.remove+"'")[0]
   1471                 post=loadjson('p/posts/'+i.remove+'/meta')
   1472                 if post['creator'] == session.user:
   1473                     os.makedirs(basedir+'u/'+session.user+'/deleted/', exist_ok=True)
   1474                     os.makedirs(basedir+'p/deleted/', exist_ok=True)
   1475                     os.system('mv '+basedir+'u/'+session.user+'/posts/'+i.remove+' '+basedir+'u/'+session.user+'/deleted/')
   1476                     os.system('mv '+basedir+'p/posts/'+i.remove+' '+basedir+'p/deleted/')
   1477                     print('move to a deleted folder, make backend clean things up for real')
   1478             except:
   1479                 pass
   1480         if session.login > 3:
   1481             try:
   1482                 if i.delete != '':
   1483                     return web.seeother('/remove/' + i.delete)
   1484             except:
   1485                 pass
   1486             rights = 'admin'
   1487         elif session.login > 2:
   1488             rights = 'mod'
   1489         else:
   1490             rights = 'spacer'
   1491         return rendersplash.heartranked(markdown, visitors, total, unique, logged, rights, session.user, getlikes, formattime, feedbase, tot, limit, offset, bildpersida, session.search, bilder, searchform, getcombines, timebase, getfeed, getcombofeed, userimage, postexist, i.show, loadjson, loadtext, len, heart, hearted, sitename, siteslogan, siterendered, siteurl, session.usrfeed)
   1492     def POST(self):
   1493         searchform = self.form()
   1494         i = web.input()
   1495         if i.search != '':
   1496             raise web.seeother('/?search='+i.search)
   1497 
   1498 def zippitandshippit(postid):
   1499     #also zippit here!
   1500     os.makedirs(basedir+'r/trusted/'+session.user+'/', exist_ok=True)
   1501     #os.system('zip -r '+basedir+'p/zipped/'+session.postid+'.zip '+basedir+'p/posts/'+session.postid )
   1502     os.system('cd '+basedir+'p/posts/ && zip -o -r '+postid+'.zip '+postid )
   1503     os.system('mv '+basedir+'p/posts/'+postid+'.zip '+basedir+'/p/zipped/')
   1504     #LETS SHIPPIT!
   1505     trustedlist=[]
   1506     trusted=os.listdir(basedir+'r/trusted/'+session.user+'/')
   1507     for t in trusted:
   1508         trusted=loadjson('r/trusted/'+session.user+'/'+t)
   1509         trustedlist.append(trusted)
   1510     for t in trustedlist:
   1511         url=t['servername']+':'+t['port']
   1512         for a in allowedchar:
   1513             if '.'+a in t['servername']: #is webaddress use https
   1514                 url='https://'+t['servername']
   1515         passcode=t['password']
   1516         trustedlogin = ['curl','-X','POST', url+'/login', '-i', '-b', basedir+'/sessions/sessions-'+session.user, '-c',basedir+'/sessions/sessions-'+session.user, '-d', 'user='+t['user'], '-d', 'password='+passcode]
   1517         subprocess.check_output(trustedlogin)
   1518         #OK GOT EM COOKIES LES DO IT DO IT DO IT SHIPPIT!
   1519         shippit = ['curl','-X', 'POST', '--verbose', '--header', 'Content-Type: multipart/form-data', '-F', 'files=@'+basedir+'p/zipped/'+postid+'.zip;type=application/zip', '-b', basedir+'/sessions/sessions-'+session.user, '-c',basedir+'/sessions/sessions-'+session.user, url+'/upload']
   1520         subprocess.check_output(shippit)
   1521 
   1522 storage = {"content": ""}
   1523 class editor:
   1524     def GET(self):
   1525         if logged():
   1526             i = web.input(publish=None, public=None, new=None, combine=None, remix=None)
   1527             if i.combine != None:
   1528                 if session.user:
   1529                     text=''
   1530                     text2=''
   1531                     session.postid = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).hexdigest()[9:36]
   1532                     os.makedirs(basedir+'u/'+session.user+'/posts/'+session.postid, exist_ok=True)
   1533                     thedict={'postid':session.postid, 'siteurl':siteurl, 'timeadded':formattime(datetime.datetime.now()), 'creator':session.user, 'combine':i.combine}
   1534                     savejson('u/'+session.user+'/posts/'+session.postid+'/meta',thedict)
   1535             if i.remix != None:
   1536                 if session.user:
   1537                     text=''
   1538                     text2=''
   1539                     try:
   1540                         #olduser = db.select('unpublished', where="postid='"+i.remix+"'")[0]
   1541                         olduser=loadjson('p/posts/'+i.remix+'/meta')
   1542                         #text = db.select('unpublished', where="postid='"+i.remix+"'")[0]
   1543                         text=loadtext('p/posts/'+i.remix+'/intro')
   1544                         #text2 = db.select('unpublished', where="postid='"+i.remix+"'")[0]
   1545                         text2=loadtext('p/posts/'+i.remix+'/post')
   1546                     except:
   1547                         pass
   1548                     try:
   1549                         olduser = olduser['creator']
   1550                     except:
   1551                         olduser = ''
   1552                     if olduser != '':
   1553                         allcreators = olduser+','+session.user
   1554                     else:
   1555                         allcreators = session.user
   1556                     session.postid = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).hexdigest()[9:36]
   1557                     #db.insert('unpublished', postid=session.postid, description=text, description2=text2, timeadded=formattime(datetime.datetime.now()), creator=allcreators, remix=i.remix)
   1558                     os.makedirs(basedir+'u/'+session.user+'/posts/'+session.postid, exist_ok=True)
   1559                     thedict={'postid':session.postid,  'siteurl':siteurl,'timeadded':formattime(datetime.datetime.now()), 'creator':allcreators, 'remix':i.remix}
   1560                     os.makedirs(basedir+'p/posts/'+i.remix+'/remix', exist_ok=True)
   1561                     savetext('p/posts/'+i.remix+'/remix/'+session.postid,session.user)
   1562 
   1563                     savejson('u/'+session.user+'/posts/'+session.postid+'/meta',thedict)
   1564                     savetext('u/'+session.user+'/posts/'+session.postid+'/post', text2)
   1565                     savetext('u/'+session.user+'/posts/'+session.postid+'/intro', text)
   1566             if session.postid != '':
   1567                 if i.public=='yes':
   1568                     try:
   1569                         #text = db.select('published', where="postid='"+session.postid+"'")[0]
   1570                         text=loadtext('p/posts/'+session.postid+'/intro')
   1571                     except:
   1572                         session.postid = ''
   1573                 else:
   1574                     try:
   1575                         #text = db.select('unpublished', where="postid='"+session.postid+"'")[0]
   1576                         text=loadtext('u/'+session.user+'/posts/'+session.postid+'/intro')
   1577                     except:
   1578                         text = ''
   1579                 if i.public=='yes':
   1580                     try:
   1581                         #text2 = db.select('published', where="postid='"+session.postid+"'")[0]
   1582                         text2=loadtext('p/posts/'+session.postid+'/post')
   1583                     except:
   1584                         session.postid = ''
   1585                 else:
   1586                     try:
   1587                         #text2 = db.select('unpublished', where="postid='"+session.postid+"'")[0]
   1588                         text2=loadtext('u/'+session.user+'/posts/'+session.postid+'/post')
   1589                     except:
   1590                         text2=''
   1591             else:
   1592                 text = ''
   1593                 text2 = ''
   1594             if i.new == 'yes':
   1595                 session.postid = ''
   1596                 raise web.seeother('/editor')
   1597             if i.publish == 'yes' and text != '' and i.public == None and logged() and len(text) < 256:
   1598                 c=loadjson('u/'+session.user+'/posts/'+session.postid+'/meta')
   1599                 if 'combine' in c:
   1600                     if c['combine'] != '':
   1601                         #calculate comborank
   1602                         os.makedirs(basedir+'p/posts/'+c['combine']+'/combos', exist_ok=True)
   1603                         savetext('p/posts/'+c['combine']+'/combos/'+session.postid,session.user)
   1604                         os.makedirs(basedir+'p/heartrank/',exist_ok=True)
   1605                         try:
   1606                             l=len(os.listdir(basedir+'p/posts/'+session.postid+'/combos/'))
   1607                         except:
   1608                             l=0
   1609                         thedict={'combos':l}
   1610                         savejson('p/posts/'+c['combine']+'/meta',thedict)
   1611                         os.system('rm '+basedir+'p/comborank/'+session.postid+'-'+str(int(l)).zfill(16))
   1612                         os.system('cp '+basedir+'p/posts/'+session.postid+'/meta '+basedir+'p/comborank/'+session.postid+'-'+str(int(l)).zfill(16))
   1613                 description1 = text
   1614                 description2 = text2
   1615                 soundname = safe_filename(description1[0:27])
   1616                 thedict={'soundname':soundname}
   1617                 savejson('u/'+session.user+'/posts/'+session.postid+'/meta',thedict)
   1618                 #session.postid = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).hexdigest()[9:36]
   1619                 os.system('cp -r '+basedir+'u/'+session.user+'/posts/'+session.postid+' '+basedir+'p/posts/')
   1620                 #symlinkthis(session.postid, session.user)
   1621                 #also zippit here!
   1622                 #os.system('zip -r '+basedir+'p/zipped/'+session.postid+'.zip '+basedir+'p/posts/'+session.postid )
   1623                 #os.system('cd '+basedir+'p/posts/ && zip -o -r '+session.postid+'.zip '+session.postid )
   1624                 #os.system('mv '+basedir+'p/posts/'+session.postid+'.zip '+basedir+'/p/zipped/')
   1625                 if zipandship == 'yes' or zipandship == 'True' or zipandship == 'y' or zipandship == 'Y':
   1626                     zippitandshippit(session.postid)
   1627                 raise web.seeother('/')
   1628                 #db.insert('pawning', pawning=i.remix, name=session.user, timeadded=formattime(datetime.datetime.now()))
   1629             return rendersplash.editor(storage, text, text2, markdown, safe_filename, session.postid, i.public, logged(), session.user, i.combine, i.remix)
   1630 
   1631 class savepost:
   1632     def POST(self):
   1633         data = json.loads(web.data())
   1634         text = data.get("text", "")
   1635         text2 = data.get("text2", "")
   1636         if session.postid == '':
   1637             session.postid = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).hexdigest()[9:36]
   1638             os.makedirs(basedir+'u/'+session.user+'/posts/'+session.postid,exist_ok=True)
   1639             thedict={'postid':session.postid, 'siteurl':siteurl, 'timeadded':formattime(datetime.datetime.now()), 'creator':session.user}
   1640             savejson('u/'+session.user+'/posts/'+session.postid+'/meta',thedict)
   1641             savetext('u/'+session.user+'/posts/'+session.postid+'/intro', text)
   1642             savetext('u/'+session.user+'/posts/'+session.postid+'/post', text2)
   1643         else:
   1644             os.makedirs(basedir+'u/'+session.user+'/posts/'+session.postid,exist_ok=True)
   1645             thedict={'postid':session.postid, 'siteurl':siteurl, 'timeadded':formattime(datetime.datetime.now()), 'creator':session.user}
   1646             savejson('u/'+session.user+'/posts/'+session.postid+'/meta',thedict)
   1647             savetext('u/'+session.user+'/posts/'+session.postid+'/intro', text)
   1648             savetext('u/'+session.user+'/posts/'+session.postid+'/post', text2)
   1649             print('post saved!')
   1650         return "ok"  # simple response
   1651 
   1652 class imageapi:
   1653     def POST(self):
   1654         action = ''
   1655         data = json.loads(web.data())
   1656         image = data.get("image", "")
   1657         action = data.get("action", "")
   1658         original_thumb = Image.open(staticdir+'users/'+session.user+'/images/thumb/'+image) 
   1659         original_web = Image.open(staticdir+'users/'+session.user+'/images/web/'+image) 
   1660         if action != '':
   1661             if action == 'rotateright' and image != None:
   1662                 o_thumb=original_thumb.rotate(270)
   1663                 o_web=original_web.rotate(270)
   1664             if action == 'rotateleft' and image != None:
   1665                 o_thumb=original_thumb.rotate(90)
   1666                 o_web=original_web.rotate(90)
   1667             o_thumb.save(staticdir+'users/'+session.user+'/images/thumb/'+image)
   1668             o_web.save(staticdir+'users/'+session.user+'/images/web/'+image)
   1669         return  # simple response
   1670 
   1671 class rendered:
   1672     def GET(self):
   1673         description=''
   1674         description2=''
   1675         try:
   1676             description=loadtext('u/'+session.user+'/posts/'+session.postid+'/intro')
   1677         except:
   1678             pass
   1679         try:
   1680             description2=loadtext('u/'+session.user+'/posts/'+session.postid+'/post')
   1681         except:
   1682             pass
   1683         return markdown.markdown(description+'\n\n---\n\n'+description2)
   1684 
   1685 def get_new_frames(gif, scale):
   1686     new_frames = []
   1687     actual_frames = gif.n_frames
   1688     for frame in range(actual_frames):
   1689         gif.seek(frame)
   1690         new_frame = Image.new('RGBA', gif.size)
   1691         new_frame.paste(gif)
   1692         new_frame.thumbnail(scale, Image.Resampling.LANCZOS)
   1693         new_frames.append(new_frame)
   1694     return new_frames
   1695 
   1696 def save_new_gif(new_frames, old_gif_information, new_path):
   1697     new_frames[0].save(new_path,
   1698                        save_all = True,
   1699                        append_images = new_frames[1:],
   1700                        duration = old_gif_information['duration'],
   1701                        loop = old_gif_information['loop'],
   1702                        background = old_gif_information['background'],
   1703                        extension = old_gif_information['extension'] ,
   1704                        transparency = old_gif_information['transparency'])
   1705 
   1706 def scale_gif(path, scale, new_path=None):
   1707     gif = Image.open(path)
   1708     if not new_path:
   1709         new_path = path
   1710     old_gif_information = {
   1711         'loop': bool(gif.info.get('loop', 1)),
   1712         'duration': gif.info.get('duration', 40),
   1713         'background': gif.info.get('background', 223),
   1714         'extension': gif.info.get('extension', (b'NETSCAPE2.0')),
   1715         'transparency': gif.info.get('transparency', 223)
   1716     }
   1717     new_frames = get_new_frames(gif, scale)
   1718     save_new_gif(new_frames, old_gif_information, new_path)
   1719 
   1720 def getallmedia(media_dir, extensions):
   1721     files = []
   1722     for ext in extensions:
   1723         c=list(Path(media_dir).rglob('*.'+ext))
   1724         for a in c:
   1725             files.append(str(a))
   1726     print('wasassssup!?')
   1727     print(files)
   1728     return files
   1729 
   1730 def symlinkmedia(mediafile,postid,user): 
   1731     fixstatic = mediafile.split('/posts/'+postid+'/')[1]
   1732     symlinkfile = staticdir + 'users/' + user + '/'+fixstatic
   1733     symlinkdir=symlinkfile.rsplit('/',1)[0]
   1734     os.makedirs(symlinkdir, exist_ok=True)
   1735     os.system('ln -sf '+mediafile+' '+symlinkfile)
   1736 
   1737 def symlinkthis(postid,user):
   1738     extensions=['zip','pdf','txt','md','mp4','jpeg','jpg','png','gif','wav','flac','mp3','ogg']
   1739     mediafiles = getallmedia(basedir+'u/'+user+'/posts/'+postid,extensions)
   1740     for m in mediafiles:
   1741         symlinkmedia(m,postid,user)
   1742     print(mediafiles)
   1743 
   1744 #symlinkthis('cd449464ec08fc7aa967d9b2795')
   1745 
   1746 class upload:
   1747     def POST(self):
   1748         if logged():
   1749             if session.postid == '':
   1750                 session.postid = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).hexdigest()[9:36]
   1751                 os.makedirs(basedir+'u/'+session.user+'/posts/'+session.postid,exist_ok=True)
   1752             try:
   1753                 extensions=['zip','pdf','txt','md','mp4','jpeg','jpg','png','gif','wav','flac','mp3','ogg']
   1754                 saved_files = []
   1755                 # Best way for multiple files in web.py
   1756                 input_data = web.webapi.rawinput()
   1757                 uploaded = input_data.get('files')
   1758                 print('LETS GO!')
   1759                 print(session.user)
   1760                 print(input_data)
   1761                 print(uploaded)
   1762                 if not isinstance(uploaded, list):
   1763                     uploaded = [uploaded] if uploaded else []
   1764                 for f in uploaded:
   1765                     if f and hasattr(f, 'filename') and f.filename:
   1766                         # Sanitize filename a bit
   1767                         imgdir = basedir + 'u/' + session.user + '/temp/'
   1768                         os.system('mkdir -p ' + imgdir)
   1769                         safe_name = safe_filename(os.path.basename(f.filename))
   1770                         filepath = os.path.join(imgdir, safe_name)
   1771                         with open(filepath, 'wb') as out:
   1772                             out.write(f.file.read())  # Important: use .file.read()
   1773                         saved_files.append(safe_name)
   1774                         ##---------- UPLOAD SOUND ----------
   1775                         imgname=filepath.split('/')[-1] # splits the and chooses the last part (the filename with extension)
   1776                         filetype = imgname.split('.')[-1].lower()
   1777                         soundfile=safe_name
   1778                         print(filetype)
   1779                         if filetype == 'zip':
   1780                             print('incoming!')
   1781                             usersound = basedir + 'u/' + session.user + '/zipped/'
   1782                             os.system('mkdir -p ' + usersound)
   1783                             os.system('mv ' + imgdir + soundfile + ' ' + usersound + soundfile)
   1784                             os.system('cd '+usersound+' && unzip -o '+soundfile+' -d '+basedir+'u/'+session.user+'/posts/')
   1785                             os.system('cd '+usersound+' && unzip -o '+soundfile+' -d '+basedir+'p/posts/')
   1786                             mediafiles = getallmedia(basedir+'p/posts/'+soundfile.split('.zip')[0],extensions)
   1787                             print('wowoweewaa')
   1788                             print(basedir+'p/posts/'+soundfile.split('.zip')[0])
   1789                             print(mediafiles)
   1790                             for m in mediafiles:
   1791                                 symlinkmedia(m,soundfile.split('.zip')[0],session.user)
   1792                         elif filetype == 'pdf' or filetype == 'txt' or filetype == 'md':
   1793                             usersound = basedir + 'u/' + session.user + '/posts/'+session.postid+'/docs/'
   1794                             os.system('mkdir -p ' + usersound)
   1795                             os.system('mv ' + imgdir + soundfile + ' ' + usersound + soundfile)
   1796                             symlinkmedia(usersound + soundfile,session.postid,session.user)
   1797                         elif filetype == 'mp4':
   1798                             usersound = basedir + 'u/' + session.user + '/posts/'+session.postid+'/films/'
   1799                             os.system('mkdir -p ' + usersound)
   1800                             os.system('mv ' + imgdir + soundfile + ' ' + usersound + soundfile)
   1801                             symlinkmedia(usersound + soundfile,session.postid,session.user)
   1802                         elif filetype == 'jpeg' or filetype == 'jpg' or filetype == 'png' or filetype == 'gif':
   1803                             usersound = basedir + 'u/' + session.user + '/posts/'+session.postid+'/images/'
   1804                             os.system('mkdir -p ' + usersound)
   1805                             os.makedirs(usersound + 'web/', exist_ok=True)
   1806                             os.makedirs(usersound + 'thumb/', exist_ok=True)
   1807                             os.system('mv ' + imgdir + soundfile + ' ' + usersound + soundfile)
   1808                             symlinkmedia(usersound + soundfile,session.postid,session.user)
   1809                             if filetype == 'gif':
   1810                                 scale_gif(usersound+soundfile, [900,900], usersound+'web/'+soundfile)
   1811                                 scale_gif(usersound+soundfile, [300,300], usersound+'thumb/'+soundfile)
   1812                                 symlinkmedia(usersound + 'web/' + soundfile,session.postid,session.user)
   1813                                 symlinkmedia(usersound + 'thumb/' + soundfile,session.postid,session.user)
   1814                             else:
   1815                                 ##---------- OPEN FILE & CHEKC IF JPEG --------
   1816                                 image = Image.open(usersound + soundfile)
   1817                                 ##---------- RESIZE IMAGE -----------
   1818                                 image.thumbnail((900,900), Image.Resampling.LANCZOS)
   1819                                 image.save(usersound + 'web/' + soundfile)
   1820                                 symlinkmedia(usersound + 'web/' + soundfile,session.postid,session.user)
   1821                                 image.thumbnail((300,300), Image.Resampling.LANCZOS)
   1822                                 image.save(usersound + 'thumb/' + soundfile)
   1823                                 symlinkmedia(usersound + 'thumb/' + soundfile,session.postid,session.user)
   1824                                 print('images resized images')
   1825                         elif filetype == 'wav' or filetype == 'flac' or filetype == 'mp3' or filetype == 'ogg':
   1826                             usersound = basedir + 'u/' + session.user + '/posts/'+session.postid+'/sounds/'
   1827                             os.system('mkdir -p ' + usersound)
   1828                             os.system('mv ' + imgdir + soundfile + ' ' + usersound + soundfile)
   1829                             soundlenght = os.popen('mediainfo --Inform="General;%Duration%" ' + usersound + soundfile).read()
   1830                             print('sound lenght:' + str(soundlenght))
   1831                             soundtype = os.popen('mediainfo --Inform="General;%Format%" ' + usersound + soundfile).read()
   1832                             print(soundtype)
   1833                             if 'Ogg' in soundtype:
   1834                                 #os.system('ffmpeg -i '+usersound+soundfile+' '+usersound+soundname+'.wav')
   1835                                 print('ogg file found, converting to flac')
   1836                                 os.system('ffmpeg -i ' + usersound + soundfile +' '+ usersound + soundname + '.flac') 
   1837                                 print('converting to mp3')
   1838                                 os.system('ffmpeg -y -loglevel 1 -i ' + usersound + soundname + '.flac -c:a libmp3lame -b:a 192k ' + usersound + soundname + '.mp3') 
   1839                             if 'MPEG Audio' in soundtype:
   1840                                 print('mp3 file found, converting to flac')
   1841                                 os.system('ffmpeg -i ' + usersound + soundfile + ' ' + usersound + soundname + '.flac') 
   1842                                 print('converting to ogg')
   1843                                 os.system('ffmpeg -i ' + usersound + soundname +'.flac '+ usersound + soundname + '.ogg') 
   1844                                 print('Wave file found, converting to flac')
   1845                             if 'Wave' in soundtype:
   1846                                 print('Wave file found, converting to flac')
   1847                                 os.system('flac ' + usersound + soundfile + ' ' + usersound + soundname + '.flac') 
   1848                                 os.system('sox -V1 ' + usersound + soundfile + ' ' + usersound + soundname + '.ogg') 
   1849                                 os.system('ffmpeg -y -loglevel 1 -i ' + usersound + soundfile + ' -c:a libmp3lame -b:a 192k ' + usersound + soundname + '.mp3') 
   1850                             if 'FLAC' in soundtype:
   1851                                 print('FLAC file found, converting to mp3 and ogg')
   1852                                 os.system('sox -V1 ' + usersound + soundfile + ' ' + usersound + soundname + '.ogg') 
   1853                                 os.system('ffmpeg -y -loglevel 1 -i ' + usersound + soundfile + ' -c:a libmp3lame -b:a 192k ' + usersound + soundname + '.mp3')
   1854                         saved_files.append(safe_name)
   1855                         print(f"✅ Saved: {safe_name}")  # This will show in console for debugging
   1856                     else:
   1857                         print("⚠️ Skipped invalid file object")
   1858             except Exception as e:
   1859                 print("Upload error:", str(e))
   1860                 return f"❌ Error: {str(e)}"
   1861             if saved_files:
   1862                 return f"✅ Successfully uploaded {len(saved_files)} file(s): {', '.join(saved_files)}"
   1863             else:
   1864                 return "❌ No files received. Check console for details."
   1865         else:
   1866             print('access denied')
   1867 
   1868 class uploads:
   1869     def GET(self):
   1870         if logged():
   1871             uploaded = getfiles(staticdir+'upload/')
   1872             return render.uploads(uploaded)
   1873 
   1874 class pull:
   1875     def GET(self):
   1876         if logged():
   1877             i = web.input(name=None,postid=None,passcode=None)
   1878             posts = get_dirs_by_time(basedir+'p/posts')[postid:]
   1879             zippandshipp=[]
   1880             if i.name!=None:
   1881                 users = os.listdir(basedir+'r/users/')
   1882                 for r in users:
   1883                     if r['name']==i.name:
   1884                         for p in posts:
   1885                             if p['creator']==p.name:
   1886                                 zippandshipp.append(p['postid'])
   1887             print(zippandshipp)
   1888             for z in zippandshipp:
   1889                 zippitandshippit(z)
   1890 
   1891 #Load from settings
   1892 standalone = settings.standaloneserver
   1893 if standalone == 'yes' or standalone == 'True' or standalone == 'y' or standalone == 'Y':
   1894     app.run()
   1895 else:
   1896     application = app.wsgifunc()