import datetime, os, re from bottle import error, response, route, run, static_file, template, TEMPLATE_PATH # Remove links, line breaks from snippet def clean(result): result = result.replace('\n','') result = result.replace('
','') result = re.sub(r'', '', result) result = re.sub(r'', '', result) result = re.sub(r'', '', result) result = result.replace('','') result = re.sub(r'','',result) result = re.sub(r'','',result) result = result.replace('
','') result = result.replace('
','') result = result.replace('','') result = result.replace('','') return result def clean_tags(raw): cleanr = re.compile('<.*?>') cleantext = re.sub(cleanr, '', raw) return cleantext def find_year(): now = datetime.datetime.now() return now.strftime('%Y') def find_gallery(name): gal = [name] if name == "Abbey": gal.append("FlightRising") gal.append([["dragon.png","lazy Abbey"], ["FlightRising.png","Abbey's sprite in FlightRising"],["BlackReshiram_Artfight.png","2022 Artfight attack by BlackReshiram"]]) if name == "Aloin": gal.append("Sims") gal.append([["alchemy.png","Morning alchemy before work"],["dream.jpg","Stupid dream bubbles"]]) if name == "Angel": gal.append("Blessfrey") gal.append([["Chibipixel.png","One of Angel's sprites. I prefer taller, less cartoony sprites, but this style is so popular it was worth trying."],["girls.png","Angel, Chloe, and Tessa"],["AngelHeadshot.png","Headshot of Angel and all her hair"]]) if name == "Aries": gal.append("Blessfrey") gal.append([["Aries.png","pencil headshot"]]) if name == "Aristen": gal.append("Black Desert Online") gal.append([["menu.jpg","Aristen's fancy set"], ["scarf.jpg", "Newbie Aristen"], ["marine.jpg","Aristen in the Epheria Marine Classic Set"]]) if name == "Belfry": gal.append("City of Heroes") gal.append([["name.png","desc"]]) if name == "Bless": gal.append("Blessfrey") gal.append([["name.png","desc"]]) if name == "Bijoux": gal.append("Sims") gal.append([["alchemy.png","Morning alchemy before work"],["dream.jpg","Stupid dream bubbles"]]) if name == "Calder": gal.append("FlightRising") gal.append([["pixelheadshot.png", "Calder's human form"], ["FlightRising.png","Calder\'s sprite in FlightRising"]]) if name == "Cass": gal.append("Beloved of the Moon") gal.append([["name.png","desc"]]) if name == "CatMan": gal.append("City of Heroes") gal.append([["name.png","desc"]]) if name == "Chandra": gal.append("Hello Traveler") gal.append([["name.png","desc"]]) if name == "Chimchooree": gal.append("Aion") gal.append([["couture.jpg","Chimchooree in her oyster pink tiered couture dress"],["Headshot.jpg","Her closeup."]]) if name == "Chloe": gal.append("Blessfrey") gal.append([["girls.png","Angel, Chloe, and Tessa"]]) if name == "Colt": gal.append("Hello Traveler") gal.append([["name.png","desc"]]) if name == "Coye": gal.append("Verpets") gal.append([["name.png","desc"]]) if name == "Dia": gal.append("Blessfrey") gal.append([["name.png","desc"]]) if name == "Dymn": gal.append("Dymn") gal.append([["name.png","desc"]]) if name == "Eden": gal.append("Blessfrey") gal.append([["name.png","desc"]]) if name == "Faber": gal.append("Sims") gal.append([["alchemy.png","Morning alchemy before work"],["dream.jpg","Stupid dream bubbles"]]) if name == "Fifi": gal.append("Sims") gal.append([["mirrorofvenus.jpg","Fifi debating herself in the mirror"],["guitarpractice.jpg","Fifi seeing how her old guitar sounds"]]) if name == "Freefall": gal.append("13th") gal.append([["name.png","desc"]]) if name == "Freya": gal.append("Guild Wars") gal.append([]) if name == "Funwa": gal.append("Beloved of the Moon") gal.append([["name.png","desc"]]) if name == "Helba": gal.append("Verpets") gal.append([["name.png","desc"]]) if name == "Helia": gal.append("Blessfrey") gal.append([["AnimeEnding.png","A still from an attempt to emulate the Little Busters ending animation. Man, animating is hard."],["wm.png","wip lol"],["Girls.png","Helia and Tessa"],["Headshot.png","emulating the art style of Battle Girl High School"]]) if name == "Helmut": gal.append("Sims") gal.append([["alchemy.png","Morning alchemy before work"],["dream.jpg","Stupid dream bubbles"]]) if name == "HOME": gal.append("HOME") gal.append([["name.png","desc"]]) if name == "Leslie": gal.append("Guild Wars") gal.append([["name.png","desc"]]) if name == "Lisbet": gal.append("Hello Traveler") gal.append([["name.png","desc"]]) if name == "Lune": gal.append("Persona") gal.append([["RingOfFire.jpg","Lune in the Ring of Fire in her Vabbian"],["HallOfMonuments.png","My Guild Wars Necromancer showing off her Hall of Monuments"],["PhariseeFlying.jpg","My Aion Elysian Spiritmaster with really pretty hair"],["LuneMarine.png","Lune Marine, my gold side resistance Water Controller in City of Heroes"],["ArcheageGuild.jpg","My ArcheAge dwarf ghost girl with gold-dipped hair and a frilly gown, sitting with her guildmates"],["Pixelmon.png","Customs Officer Lune and her sidekick Lilligant in Pixelmon"],["tinypool.png","Bikini Lune in a tiny pool with her kitty in Minecraft"]]) if name == "Nephele": gal.append("Verpets") gal.append([["name.png","desc"]]) if name == "Newcomer": gal.append("Newcomer") gal.append([["name.png","desc"]]) if name == "Night": gal.append("Blessfrey") gal.append([]) if name == "Pixie": gal.append("City of Heroes") gal.append([["alchemy.png","Morning alchemy before work"],["dream.jpg","Stupid dream bubbles"]]) if name == "Rodolphe": gal.append("Sims") gal.append([["alchemy.png","Morning alchemy before work"],["dream.jpg","Stupid dream bubbles"]]) if name == "Rune": gal.append("Blessfrey") gal.append([["AnimeEnding.png","A still from an attempt to emulate the Little Busters ending animation. Man, animating is hard."],["Bless+Rune.png","Rune and his dinosaur mother"],["Blessfrey.png","Emulating old RPG coverart like Elden Gate in mixed media. Don't feel like working on it anymore."],["FlightRising.png","his oc dragon form in FlightRising (I still can't believe they added not-styracosaur to the game!)"],["RuneHeadshot.png","emulating the art style of Battle Girl High School"],["CawfeeCakes_Artfight.png","2022 Artfight attack by CawfeeCakes"],["Teriuuuu_Artfight.png","2020 Artfight attack by Teriuuuu"]]) if name == "Ryada": gal.append("Guild Wars") gal.append([[]]) if name == "Silke": gal.append("Verpets") gal.append([["Silke.png","emulating the art style of Yuu Watase in pixelart"],["Verpets.png","Silke's sprite in Verpets"]]) if name == "Tessa": gal.append("Blessfrey") gal.append([["Tessa.png","Pixelart of Tessa in athletic wear"],["picrew.png","made in あの子がこっちを見ている on Picrew"],["FlightRising.png","her oc dragon form in FlightRising"],["RamblingRoses_Artfight.png","2020 Artfight attack by RamblingRoses"],["Lune_Archon_Artfight.jpeg","2022 Artfight attack by Lune_Archon"],["ElissaKarminakria_Artfight.png","2020 Artfight attack by ElissaKarminakria"],["gemhue_Artfight.png","2020 Artfight attack by gemhue"],["Cyan_moo_Artfight.png","2020 Artfight attack by Cyan_moo"]]) if name == "Tilhar": gal.append("Beloved of the Moon") gal.append([["name.png","desc"]]) if name == "Tilly": gal.append("Sims") gal.append([["alchemy.png","Morning alchemy before work"],["dream.jpg","Stupid dream bubbles"]]) if name == "Trace": gal.append("13th") gal.append([["name.png","desc"]]) if name == "Window": gal.append("Dymn") gal.append([["name.png","desc"]]) if name == "WISE": gal.append("Blessfrey") gal.append([["WISE.png","WISE processing a query"],["CityOfHeroes.png","I made WISE in City of Heroes, too. She's an Electric/Empathy Controller."]]) return gal def prepare_profile(loc, char_name): result = [] for i in ["label","basics","story","desc"]: string = "" with open(loc + char_name + "-" + i) as f: lines = f.readlines() for line in lines: string += line result.append(string) return result # Return list of snippets using list of articles def list_snippets(articles): loc = 'diary/entries/' limit = 4 total = len(articles) result = [] for article in articles: path = loc + article text = [] a = [] length = 0 text = article2list(article, loc) a.append(find_title(text)) a.append(prepare_article(text, path)) a.append(find_timestamp(text)) a.append(find_url(path)) a.append(find_social_title(text)) a.append(find_tags(text)) result.append(a) return result # List latest 5 articles as headline links def list_headlines(articles): loc = 'diary/entries/' result = [] text = [] for article in articles: path = loc + article b = [] b.append(path) with open(path) as f: f.readline() text = f.readline() b.append(clean(text.replace('
',''))) result.append(b) return result # Return first two sentences of article + " ... " def snip_sentence(article, path): article = clean(article) limit = 100 result = article[0:min(len(article),limit)] result = result.rsplit(' ',1)[0] return result + " ... " # Return first 300 words of article + " ... " def snip_article(article, path): article = clean(article) limit = 300 result = article[0:min(len(article),limit)] result = result.rsplit(' ',1)[0] return result + " ... " # Snip article and close any open list tags def prepare_article(text, path): content = snip_article(find_content(text), path) if content.count(''): content += '' return content # Return article text without HTML header def find_content(text): length = len(text) content = "" # form a string from relevant lines of the article pos = 0 for line in text: # skip to line 5 if pos > 4 and pos < length: content += line pos += 1 return content def find_tags(text): new = text[3].replace('
','') new = new.replace('\n','') new = new.split(" ") final = [] for n in new: if len(n) <= 0: new.remove(n) if '#' in n: final.append(n) final.sort() return final # Return title of article, formatted for sharing via social media def find_social_title(text): return clean(text[1]).replace(' ','+') # Return URL of article def find_url(path): return '/' + path.replace('.tpl','') # Return clean timestamp def find_timestamp(text): return text[2].replace('
','') # Return clean title def find_title(text): return clean(text[1]) # Return list of files with given tag def pull_tag(files, tag): pull = [] for f in files: tags = find_tags(article2list(str(f), 'diary/entries/')) if "#" + tag in tags: pull.append(f) pull.sort(reverse=True) return pull def retrieve_diary_entry_content(page,loc): text = [] string = "" with open(loc + str(page)) as f: lines = f.readlines() for line in lines: if lines.index(line) >= 4: string += line return string def list_rec(page): loc = 'diary/entries/' result = [] rec = [] comment = "" if isinstance(page, int): # Collect recommended articles from comment line with open('diary/entries/' + str(page)) as f: comment = f.readline() comment = comment.replace('','') comment = comment.replace(' ','') comment = clean(comment) rec = comment.split(',') # Convert into array for template to display for article in rec: if is_it_time(article): path = loc + article data = [] try: with open(path) as f: f.readline() data.append(clean(f.readline().replace('
',''))) data.append(path) result.append(data) except EnvironmentError: print("No article @ " + path) return result def prepare_diary_entry(page, loc): result = [] with open(loc + str(page)) as f: text = [] text = article2list(str(page), loc) result.append(find_title(text)) result.append(retrieve_diary_entry_content(page, loc)) result.append(find_timestamp(text)) result.append(find_url(loc + str(page))) result.append(find_social_title(text)) result.append(find_tags(text)) return result # Return article as list of lines of text def article2list(article, loc): text = [] with open(loc + article) as f: text = f.readlines() return text # return list of diary entry tags, sorted by frequency def fill_word_cloud(files): tags = [] for f in files: temp = find_tags(article2list(str(f), 'diary/entries/')) for t in temp: tags.append(t) tags.sort() cloud = [] i = 0 while i < 24: if len(tags) > 0: top = max(set(tags), key = tags.count) cloud.append(top) tags[:] = [x for x in tags if x != top] i += 1 return cloud def curate_files(files): # remove folders if 'hold' in files: files.remove('hold') # remove clean = [] for f in files: if f == '': files.remove(f) if is_it_time(f): clean.append(f) return clean # Sort diary - newest to oldest def sort_files(files): files.sort(reverse=True) return files # Return list of all diary entries (exclude raws + extras) def gather_files(loc): files = os.listdir(loc) return files def gather_and_sort(loc): return sort_files(curate_files(gather_files(loc))) def is_it_time(date): if date == '': return False today = datetime.datetime.now() today_string = today.strftime("%y") + today.strftime("%m") + today.strftime("%d") return int(date) <= int(today_string) ## Static ## # Serve CSS @route('/static/css/') def serve_css(filename): return static_file(filename, root='static/css') # Serve fonts @route('/static/font/') def serve_font(filename): return static_file(filename, root='static/font', mimetype='text/ttf') # Serve images @route('/static/img/') def serve_img(filename): return static_file(filename, root='static/img') # Serve XML @route('/static/xml/') def serve_xml(filename): return static_file(filename, root='static/xml', mimetype='text/xml') # Downloads @route('/download/') def download(filename): return static_file(filename, root='static/extra', download=filename) ## Routes ## # Error Page @error(404) def error404(error): return "unfortunately, a 404 error. the page you're searching for doesn't exist. (or is it just in hiding?) try another page! " @error(500) def error500(error): return "unfortunately, a 500 error. something is wrong with the page you're trying to find, if it exists at all. try another page! return to blessfrey.me." @error(502) def error502(error): return "unfortunately, a 502 error. this was likely due to website maintenance. usually it'll be back up before you finish reading this, but otherwise, I'll notice something's wrong soon! return to blessfrey.me." # Art Gallery Page @route('/art') def art(): """art""" info = {'css': 'me', 'title': 'art gallery', 'year': find_year(), 'pixelart': ["Abbey_EmmArrGus_BrodyChar.png","Aisu+Luna_pix.png","artfight_blessfrey_Tessa.png","chimchooree_moonelf_twitter.png","chimchooree_silke_12colors.png","boss.png","gamecharacter.png","chimchooree_Airi.gif","customsofficer.png"], 'digital': ["WISE.png","ringoffire.jpeg"], 'fanart': ["trineaRemake.png","persona3demake.png","kamaAD1.gif"]} return template('art.tpl', info) # OC Page @route('/characters') # redirect def char3(): return char("Helia") @route('/char/') # if no OC given def char2(): return char("Helia") @route('/char/') def char(char_name): """character page""" loc = 'char/' info = {'css': 'char', 'title': 'blessfrey - characters | meet ' + char_name, 'year': find_year(), 'ocs': ["Helia", "Rune", "Tessa", "Abbey", "Calder", "Silke", "Aristen", "Chimchooree", "Lune"], 'name': char_name, 'profile': prepare_profile(loc, char_name), 'gallery': find_gallery(char_name)} abs_app_dir_path = os.path.dirname(os.path.realpath(__file__)) abs_views_path = os.path.join(abs_app_dir_path, 'views') TEMPLATE_PATH.insert(0, abs_views_path ) return template(os.path.join(abs_views_path,'char.tpl'), info) # Blessfrey Credits Page @route('/credits') def credits(): """credits""" info = {'css': 'doc', 'title': 'blessfrey credits', 'year': find_year()} return template('credits.tpl', info) # Blessfrey Demo Page @route('/demo') def demo(): """demo""" info = {'css': 'demo', 'title': 'blessfrey demo', 'year': find_year()} return template('demo.tpl', info) # Blessfrey Presskit Page @route('/blessfrey-gdd') def gdd(): """Game Design Document""" info = {'css': 'gdd', 'title': 'blessfrey gdd', 'year': find_year()} return template('bf-gdd.tpl', info) # Diary Page @route('/diary') # Start on first Diary page if no page given def diary2(): return diary(0) @route('/diary/') # Slash is optional def diary3(): return diary(0) @route('/diary/') def diary(page): """diary""" loc = 'diary/entries/' assert isinstance(page, int) info = {'css': 'diary', 'title': 'chimchooree\'s diary', 'year': find_year(), 'snippets': list_snippets(gather_and_sort(loc)), 'latest': list_headlines(gather_and_sort(loc)[0:5]), 'tags': fill_word_cloud(curate_files(gather_files(loc))), 'total': len(curate_files(gather_files(loc))), 'limit': 8, 'cluster': 3, 'page': page} return template('diary.tpl', info) # Entry Page - Feature Template - for articles @route('/diary/entries/') def entry(page): """diary entry""" if not is_it_time(page): return error404(404) loc = 'diary/entries/' info = {'css': 'entry', 'title': 'blessfrey developer diary', 'year': find_year(), 'entry': prepare_diary_entry(page, loc), 'recommends': list_rec(page), 'articles': "Articles", 'latest': list_headlines(gather_and_sort(loc)[0:5]), 'tags': fill_word_cloud(curate_files(gather_files(loc))), 'page': page} abs_app_dir_path = os.path.dirname(os.path.realpath(__file__)) abs_views_path = os.path.join(abs_app_dir_path, 'views') TEMPLATE_PATH.insert(0, abs_views_path ) return template(os.path.join(abs_views_path,'entry.tpl'), info) # Fashion Page @route('/fashion') def fashion(): """fashion""" info = {'css': 'fashion', 'title': 'blessfrey fashion', 'year': find_year()} return template('fashion.tpl', info) # Blessfrey Game Design Document Page @route('/gdd') def gdd(): """game design document""" info = {'css': 'doc', 'title': 'blessfrey game design document', 'year': find_year()} return template('bf-gdd.tpl', info) # Home Page - Index Template @route('/') def home(): """home""" loc = 'diary/entries/' info = {'css': 'index', 'title': 'chimchooree\'s dev space - blessfrey', 'year': find_year(), 'news': list_headlines(sort_files(curate_files(gather_files(loc)))[0:10])} return template('index.tpl', info) # Location Page @route('/loc/') # if no location given def loc2(): return loc("Lucrest") @route('/location/') def loc(location_name): """location page""" loc = 'location/' info = {'css': 'char', 'title': 'blessfrey - location | meet ' + location_name, 'year': find_year(), 'ocs': ["human","giant","serpent"], 'name': location_name, 'profile': prepare_profile(loc, location_name), 'gallery': find_gallery(location_name)} abs_app_dir_path = os.path.dirname(os.path.realpath(__file__)) abs_views_path = os.path.join(abs_app_dir_path, 'views') TEMPLATE_PATH.insert(0, abs_views_path ) return template(os.path.join(abs_views_path,'char.tpl'), info) # Me Page @route('/me') def me(): """me""" info = {'css': 'me', 'title': 'about me', 'year': find_year()} return template('me.tpl', info) # Blessfrey Feature Requirements Page @route('/milestones') def milestones(): """feature requirements""" info = {'css': 'doc', 'title': 'blessfrey milestones', 'year': find_year()} return template('requirements.tpl', info) # Planner Page @route('/planner') def planner(): """planner""" info = {'css': 'doc', 'title': 'personal planner', 'year': find_year()} return template('planner.tpl', info) # Blessfrey Presskit Page @route('/blessfrey-presskit') def presskit(): """presskit""" info = {'css': 'doc', 'title': 'blessfrey presskit', 'year': find_year()} return template('blessfrey-presskit.tpl', info) # Species Page @route('/race/') # if no species given def spec4(): return species("human") @route('/race/') def spec3(species_name): return species(species_name) @route('/species/') # if no species given def spec2(): return species("human") @route('/species/') def spec(species_name): """species page""" loc = 'species/' info = {'css': 'char', 'title': 'blessfrey - species | meet ' + species_name, 'year': find_year(), 'ocs': ["human","giant","serpent"], 'name': species_name, 'profile': prepare_profile(loc, species_name), 'gallery': find_gallery(species_name)} abs_app_dir_path = os.path.dirname(os.path.realpath(__file__)) abs_views_path = os.path.join(abs_app_dir_path, 'views') TEMPLATE_PATH.insert(0, abs_views_path ) return template(os.path.join(abs_views_path,'char.tpl'), info) # Blessfrey Style Guide Page @route('/style') def style(): """style guide""" info = {'css': 'doc', 'title': 'blessfrey style guide', 'year': find_year()} return template('style-guide.tpl', info) # Search Diary by Tag @route('/diary/tag/') # Start on first Diary tag page if no page given def tag2(tagin): return tag(tagin, 0) @route('/diary/tag//') # Tag Page - Diary Tag Template - list all articles for tag def tag(tagin, page): """tag page""" loc = 'diary/entries/' assert isinstance(tagin, str) assert isinstance(page, int) info = {'css': 'diary', 'title': 'blessfrey developer diary', 'year': find_year(), 'snippets': list_snippets(pull_tag(gather_and_sort(loc), tagin)), 'latest': list_headlines(gather_and_sort(loc)[0:5]), 'tags': fill_word_cloud(curate_files(gather_files(loc))), 'total': len(curate_files(gather_files(loc))), 'limit': 8, 'cluster': 3, 'page': page} return template('diary.tpl', info) # GDD Pages @route('/vibe') def vibe(): """GDD Page""" info = {'css': 'doc', 'title': 'blessfrey gdd - vibe', 'year': find_year()} return template('vibe.tpl', info) ## Main ## if __name__ == '__main__': run(host='0.0.0.0', port=9001)