2023-06-01 21:25:13 +00:00
#!/usr/bin/env python
2023-06-02 14:02:40 +00:00
from flask import (
Flask ,
render_template ,
request ,
redirect ,
Response ,
stream_with_context ,
)
2023-07-19 06:26:45 +00:00
2023-06-01 21:25:13 +00:00
from urllib . parse import quote , unquote
2024-01-16 16:13:59 +00:00
from urllib . request import Request , urlopen
from urllib . error import HTTPError
2023-06-01 21:25:13 +00:00
from traceback import print_exc
2024-01-16 16:13:59 +00:00
from urllib . parse import urljoin , urlparse
2023-06-02 14:02:40 +00:00
from argparse import ArgumentParser
2024-01-16 16:13:59 +00:00
from configparser import ConfigParser
2023-06-01 21:25:13 +00:00
2023-07-19 06:26:45 +00:00
from werkzeug . exceptions import BadRequest , abort , InternalServerError , NotFound
2024-01-16 16:13:59 +00:00
from bs4 import BeautifulSoup
2023-07-19 06:26:45 +00:00
2023-06-01 21:25:13 +00:00
import os
2024-01-16 16:13:59 +00:00
import json
import re
import logging
2024-01-17 15:43:51 +00:00
import pathlib
2024-01-16 16:13:59 +00:00
logging . basicConfig ( level = logging . DEBUG )
2023-06-01 21:25:13 +00:00
2023-06-03 07:46:28 +00:00
global_ibles = { }
2024-01-16 16:13:59 +00:00
def proxy ( url ) :
logging . debug ( f " Generating proxy URL for { url } " )
return f " /proxy/?url= { url } "
2023-06-03 07:46:28 +00:00
2024-01-16 16:13:59 +00:00
def get_typesense_api_key ( ) :
logging . debug ( " Getting Typesense API key... " )
2023-06-03 07:46:28 +00:00
2024-01-16 16:13:59 +00:00
data = urlopen ( " https://www.instructables.com/ " )
soup = BeautifulSoup ( data . read ( ) . decode ( ) , " html.parser " )
scripts = soup . select ( " script " )
2023-06-03 07:46:28 +00:00
2024-01-16 16:13:59 +00:00
for script in scripts :
if " typesense " in script . text and (
matches := re . search ( r ' " typesenseApiKey " : \ s? " (.*?) " ' , script . text )
) :
api_key = matches . group ( 1 )
logging . debug ( f " Identified Typesense API key as { api_key } " )
return api_key
logging . error ( " Failed to get Typesense API key " )
TYPESENSE_API_KEY = get_typesense_api_key ( )
def projects_search (
query = " * " ,
category = " " ,
channel = " " ,
filter_by = " featureFlag:=true " ,
page = 1 ,
per_page = 50 ,
) :
if category :
if filter_by :
filter_by + = " && "
filter_by + = f " category:= { category } "
if channel :
if filter_by :
filter_by + = " && "
filter_by + = f " channel:= { channel } "
query = quote ( query )
filter_by = quote ( filter_by )
logging . debug ( f " Searching projects with query { query } and filter { filter_by } " )
projects_headers = { " x-typesense-api-key " : TYPESENSE_API_KEY }
projects_request = Request (
f " https://www.instructables.com/api_proxy/search/collections/projects/documents/search?q= { query } &query_by=title,stepBody,screenName&page= { page } &sort_by=publishDate:desc&include_fields=title,urlString,coverImageUrl,screenName,favorites,views,primaryClassification,featureFlag,prizeLevel,IMadeItCount&filter_by= { filter_by } &per_page= { per_page } " ,
headers = projects_headers ,
)
projects_data = urlopen ( projects_request )
project_obj = json . loads ( projects_data . read ( ) . decode ( ) )
project_ibles = project_obj [ " hits " ]
logging . debug ( f " Got { len ( project_ibles ) } projects " )
return project_ibles
def update_data ( ) :
logging . debug ( " Updating data... " )
channels = [ ]
sitemap_data = urlopen ( " https://www.instructables.com/sitemap/ " )
sitemap_soup = BeautifulSoup ( sitemap_data . read ( ) . decode ( ) , " html.parser " )
main = sitemap_soup . select ( " div.sitemap-content " ) [ 0 ]
2023-06-03 07:46:28 +00:00
groups = [ ]
for group in main . select ( " div.group-section " ) :
channels . append ( group . select ( " h2 a " ) [ 0 ] . text . lower ( ) )
global_ibles [ " /projects " ] = [ ]
2024-01-16 16:13:59 +00:00
project_ibles = projects_search ( )
2023-06-03 07:46:28 +00:00
while len ( global_ibles [ " /projects " ] ) < = 0 :
2024-01-16 16:13:59 +00:00
for ible in project_ibles :
link = f " / { ible [ ' document ' ] [ ' urlString ' ] } "
img = proxy ( ible [ " document " ] [ " coverImageUrl " ] )
2023-06-03 07:46:28 +00:00
2024-01-16 16:13:59 +00:00
title = ible [ " document " ] [ " title " ]
author = ible [ " document " ] [ " screenName " ]
author_link = f " /member/ { author } "
channel = ible [ " document " ] [ " primaryClassification " ]
channel_link = f " /channel/ { channel } "
views = ible [ " document " ] [ " views " ]
favorites = ible [ " document " ] [ " favorites " ]
2023-06-03 07:46:28 +00:00
global_ibles [ " /projects " ] . append (
2024-01-17 15:43:51 +00:00
{
" link " : link ,
" img " : img ,
" title " : title ,
" author " : author ,
" author_link " : author_link ,
" channel " : channel ,
" channel_link " : channel_link ,
" views " : views ,
" favorites " : favorites ,
}
2023-06-03 07:46:28 +00:00
)
2024-01-16 16:13:59 +00:00
debugmode = os . environ . get ( " FLASK_DEBUG " , False )
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
if __name__ == " __main__ " :
parser = ArgumentParser ( )
parser . add_argument (
" -p " ,
" --port " ,
default = 8002 ,
type = int ,
help = " Port to listen on " ,
)
parser . add_argument (
" -d " ,
" --debug " ,
action = " store_true " ,
help = " Enable debug mode " ,
)
parser . add_argument (
" -l " ,
" --listen-host " ,
default = " 127.0.0.1 " ,
help = " Host to listen on " ,
)
args = parser . parse_args ( )
2024-01-16 16:13:59 +00:00
2023-06-02 14:02:40 +00:00
if args . debug :
2023-06-01 21:25:13 +00:00
debugmode = True
print ( " Loading... " )
2023-06-03 07:46:28 +00:00
update_data ( )
2023-06-02 14:02:40 +00:00
2023-06-03 07:46:28 +00:00
print ( " Started! " )
2023-06-02 14:02:40 +00:00
2023-06-03 07:46:28 +00:00
app = Flask ( __name__ , template_folder = " templates " , static_folder = " static " )
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
if debugmode :
app . logger . setLevel ( logging . DEBUG )
2023-06-01 21:25:13 +00:00
2023-06-03 07:46:28 +00:00
@app.route ( " /cron/ " )
def cron ( ) :
update_data ( )
return " OK "
2023-06-02 14:02:40 +00:00
2024-01-16 16:13:59 +00:00
2023-06-01 21:25:13 +00:00
def explore_lists ( soup ) :
list_ = [ ]
for ible in soup . select ( " .home-content-explore-ible " ) :
link = ible . a [ " href " ]
img = proxy ( ible . select ( " a img " ) [ 0 ] . get ( " data-src " ) )
alt = ible . select ( " a img " ) [ 0 ] . get ( " alt " )
title = ible . select ( " div strong a " ) [ 0 ] . text
author = ible . select ( " div span.ible-author a " ) [ 0 ] . text
author_link = ible . select ( " div span.ible-author a " ) [ 0 ] . get ( " href " )
channel = ible . select ( " div span.ible-channel a " ) [ 0 ] . text
channel_link = ible . select ( " div span.ible-channel a " ) [ 0 ] . get ( " href " )
views = 0
if ible . select ( " span.ible-views " ) != [ ] :
views = ible . select ( " span.ible-views " ) [ 0 ] . text
favorites = 0
if ible . select ( " span.ible-favorites " ) != [ ] :
favorites = ible . select ( " span.ible-favorites " ) [ 0 ] . text
2023-06-02 14:02:40 +00:00
list_ . append (
2024-01-17 15:43:51 +00:00
{
" link " : link ,
" img " : img ,
" alt " : alt ,
" title " : title ,
" author " : author ,
" author_link " : author_link ,
" channel " : channel ,
" channel_link " : channel_link ,
" favorites " : favorites ,
" views " : views ,
}
2023-06-02 14:02:40 +00:00
)
2023-06-01 21:25:13 +00:00
return list_
2023-06-02 14:02:40 +00:00
2023-06-01 21:25:13 +00:00
def member_header ( header ) :
2023-06-02 14:02:40 +00:00
avatar = proxy (
header . select ( " div.profile-avatar-container img.profile-avatar " ) [ 0 ] . get ( " src " )
)
title = header . select ( " div.profile-top div.profile-headline h1.profile-title " ) [
0
] . text
2023-06-01 21:25:13 +00:00
profile_top = header . select ( " div.profile-top " ) [ 0 ]
# stats_text = profile_top.select("div.profile-header-stats")[0]
# stats_num = header.select("div.profile-top div.profile-header-stats")[1]
location = header . select ( " span.member-location " )
if location != [ ] :
location = location [ 0 ] . text
else :
location = 0
signup = header . select ( " span.member-signup-date " )
if signup != [ ] :
signup = signup [ 0 ] . text
else :
signup = 0
instructables = header . select ( " span.ible-count " )
if instructables != [ ] :
instructables = instructables [ 0 ] . text
else :
instructables = 0
views = header . select ( " span.total-views " )
if views != [ ] :
views = views [ 0 ] . text
else :
views = 0
comments = header . select ( " span.total-comments " )
if comments != [ ] :
comments = comments [ 0 ] . text
else :
comments = 0
followers = header . select ( " span.follower-count " )
if followers != [ ] :
followers = followers [ 0 ] . text
else :
followers = 0
bio = header . select ( " span.member-bio " )
if bio != [ ] :
bio = bio [ 0 ] . text
else :
bio = " "
2024-01-17 15:43:51 +00:00
return {
" avatar " : avatar ,
" title " : title ,
" location " : location ,
" signup " : signup ,
" instructables " : instructables ,
" views " : views ,
" comments " : comments ,
" followers " : followers ,
" bio " : bio ,
}
2023-06-02 14:02:40 +00:00
2023-06-01 21:25:13 +00:00
def category_page ( path , name , teachers = False ) :
2024-01-17 15:43:51 +00:00
# TODO: Figure out why this doesn't work - probably using the search function would help...
2024-01-16 16:13:59 +00:00
try :
data = urlopen ( " https://www.instructables.com " + path )
except HTTPError as e :
abort ( e . code )
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
soup = BeautifulSoup ( data . read ( ) . decode ( ) , " html.parser " )
2023-06-01 21:25:13 +00:00
channels = [ ]
for card in soup . select ( " div.scrollable-cards-inner div.scrollable-card " ) :
link = card . a [ " href " ]
2023-06-02 14:02:40 +00:00
img = proxy (
card . select ( f " a { ' noscript ' if teachers else ' ' } img " ) [ 0 ] . get ( " src " )
)
2023-06-01 21:25:13 +00:00
title = card . select ( " a img " ) [ 0 ] . get ( " alt " )
2024-01-17 15:43:51 +00:00
channels . append ( { " link " : link , " title " : title , " img " : img } )
2023-06-01 21:25:13 +00:00
ibles = [ ]
2023-06-02 14:02:40 +00:00
for ible in soup . select (
" div.category-landing-projects-list div.category-landing-projects-ible "
) :
2023-06-01 21:25:13 +00:00
link = ible . a [ " href " ]
img = proxy ( ible . select ( " a noscript img " ) [ 0 ] . get ( " src " ) )
info = ible . select ( " div.category-landing-projects-ible-info " ) [ 0 ]
title = info . select ( " a.ible-title " ) [ 0 ] . text
author = info . select ( " span.ible-author a " ) [ 0 ] . text
author_link = info . select ( " span.ible-author a " ) [ 0 ] . get ( " href " )
channel = info . select ( " span.ible-channel a " ) [ 0 ] . text
channel_link = info . select ( " span.ible-channel a " ) [ 0 ] . get ( " href " )
stats = ible . select ( " span.ible-stats-right-col " ) [ 0 ]
views = 0
if stats . select ( " span.ible-views " ) != [ ] :
views = stats . select ( " span.ible-views " ) [ 0 ] . text
favorites = 0
if stats . select ( " span.ible-favorites " ) != [ ] :
favorites = stats . select ( " span.ible-favorites " ) [ 0 ] . text
2023-06-02 14:02:40 +00:00
ibles . append (
2024-01-17 15:43:51 +00:00
{
" link " : link ,
" img " : img ,
" title " : title ,
" author " : author ,
" author_link " : author_link ,
" channel " : channel ,
" channel_link " : channel_link ,
" views " : views ,
" favorites " : favorites ,
}
2023-06-02 14:02:40 +00:00
)
2023-06-01 21:25:13 +00:00
contests = [ ]
2023-06-02 14:02:40 +00:00
for contest in soup . select (
" div.category-landing-contests-list div.category-landing-contests-item "
) :
2023-06-01 21:25:13 +00:00
link = contest . a [ " href " ]
img = proxy ( contest . select ( " a noscript img " ) [ 0 ] . get ( " src " ) )
title = contest . select ( " a img " ) [ 0 ] . get ( " alt " )
2024-01-17 15:43:51 +00:00
contests . append ( { " link " : link , " img " : img , " title " : title } )
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
return render_template (
2024-01-17 15:43:51 +00:00
" category.html " ,
name = name ,
channels = channels ,
ibles = ibles ,
contests = contests ,
path = path ,
2023-06-02 14:02:40 +00:00
)
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
def project_list ( path , head , sort = " " ) :
2023-06-01 21:25:13 +00:00
head = f " { head + ' ' if head != ' ' else ' ' } Projects " + sort
2024-01-16 16:13:59 +00:00
path = urlparse ( path ) . path
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
if path in ( " /projects/ " , " /projects " ) :
2023-06-01 21:25:13 +00:00
ibles = global_ibles [ " /projects " ]
else :
2024-01-16 16:13:59 +00:00
if not " projects " in path . split ( " / " ) :
abort ( 404 )
2023-06-01 21:25:13 +00:00
ibles = [ ]
2024-01-16 16:13:59 +00:00
parts = path . split ( " / " )
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
category = parts [ 1 ]
channel = " " if parts [ 2 ] == " projects " else parts [ 2 ]
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
# TODO: Add pagination, popular, etc.
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
project_ibles = projects_search ( category = category , channel = channel )
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
for ible in project_ibles :
link = f " / { ible [ ' document ' ] [ ' urlString ' ] } "
img = proxy ( ible [ " document " ] [ " coverImageUrl " ] )
2023-06-02 14:02:40 +00:00
2024-01-16 16:13:59 +00:00
title = ible [ " document " ] [ " title " ]
author = ible [ " document " ] [ " screenName " ]
author_link = f " /member/ { author } "
2023-06-02 14:02:40 +00:00
2024-01-16 16:13:59 +00:00
channel = ible [ " document " ] [ " primaryClassification " ]
channel_link = f " /channel/ { channel } "
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
views = ible [ " document " ] [ " views " ]
favorites = ible [ " document " ] [ " favorites " ]
2023-06-02 14:02:40 +00:00
ibles . append (
2024-01-17 15:43:51 +00:00
{
" link " : link ,
" img " : img ,
" title " : title ,
" author " : author ,
" author_link " : author_link ,
" channel " : channel ,
" channel_link " : channel_link ,
" views " : views ,
" favorites " : favorites ,
}
2023-06-02 14:02:40 +00:00
)
2023-06-01 21:25:13 +00:00
if len ( ibles ) > = 8 :
break
2024-01-17 15:43:51 +00:00
print ( ibles )
return render_template ( " projects.html " , title = head , ibles = ibles , path = path )
2023-06-02 14:02:40 +00:00
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /sitemap/ " )
2024-01-16 16:13:59 +00:00
@app.route ( " /sitemap/<path:path> " )
def route_sitemap ( path = " " ) :
try :
data = urlopen ( " https://www.instructables.com/sitemap/ " + path )
except HTTPError as e :
abort ( e . code )
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
soup = BeautifulSoup ( data . read ( ) . decode ( ) , " html.parser " )
2023-06-01 21:25:13 +00:00
main = soup . select ( " div.sitemap-content " ) [ 0 ]
2024-01-16 16:13:59 +00:00
group_section = main . select ( " div.group-section " )
if group_section :
groups = [ ]
for group in group_section :
category = group . select ( " h2 a " ) [ 0 ] . text
category_link = group . select ( " h2 a " ) [ 0 ] . get ( " href " )
channels = [ ]
for li in group . select ( " ul.sitemap-listing li " ) :
channel = li . a . text
channel_link = li . a [ " href " ]
channels . append ( [ channel , channel_link ] )
groups . append ( [ category , category_link , channels ] )
else :
groups = [ ]
2023-06-01 21:25:13 +00:00
channels = [ ]
2024-01-16 16:13:59 +00:00
for li in main . select ( " ul.sitemap-listing li " ) :
2023-06-01 21:25:13 +00:00
channel = li . a . text
channel_link = li . a [ " href " ]
channels . append ( [ channel , channel_link ] )
2024-01-16 16:13:59 +00:00
groups . append ( [ " " , " " , channels ] )
2023-06-01 21:25:13 +00:00
2024-01-17 15:43:51 +00:00
return render_template ( " sitemap.html " , title = " Sitemap " , groups = groups )
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /contest/archive/ " )
2023-06-01 21:25:13 +00:00
def route_contest_archive ( ) :
page = 1
if request . args . get ( " page " ) != None :
page = request . args . get ( " page " )
2024-01-16 16:13:59 +00:00
try :
data = urlopen ( f " https://www.instructables.com/contest/archive/?page= { page } " )
except HTTPError as e :
abort ( e . code )
soup = BeautifulSoup ( data . read ( ) . decode ( ) , " html.parser " )
2023-06-01 21:25:13 +00:00
main = soup . select ( " div#contest-archive-wrapper " ) [ 0 ]
contest_count = main . select ( " p.contest-count " ) [ 0 ] . text
contest_list = [ ]
for index , year in enumerate ( main . select ( " div.contest-archive-list h2 " ) ) :
2023-06-02 14:02:40 +00:00
year_list = main . select (
" div.contest-archive-list div.contest-archive-list-year "
) [ index ]
2023-06-01 21:25:13 +00:00
year_name = year . text
month_list = [ ]
for month in year_list . select ( " div.contest-archive-list-month " ) :
month_name = month . select ( " h3 " ) [ 0 ] . text
month_contest_list = [ ]
for p in month . select ( " p " ) :
date = p . select ( " span " ) [ 0 ] . text
link = p . select ( " a " ) [ 0 ] . get ( " href " )
title = p . select ( " a " ) [ 0 ] . text
month_contest_list . append ( [ date , link , title ] )
month_list . append ( [ month_name , month_contest_list ] )
contest_list . append ( [ year_name , month_list ] )
pagination = main . select ( " nav.pagination ul.pagination " ) [ 0 ]
2023-06-02 14:02:40 +00:00
return render_template (
2024-01-17 15:43:51 +00:00
" archives.html " ,
title = f " Contest Archives (Page { page } ) " ,
page = page ,
contest_count = contest_count ,
pagination = pagination ,
contest_list = contest_list ,
2023-06-02 14:02:40 +00:00
)
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /contest/<contest>/ " )
2023-06-01 21:25:13 +00:00
def route_contest ( contest ) :
2024-01-16 16:13:59 +00:00
try :
data = urlopen ( f " https://www.instructables.com/contest/ { contest } / " )
except HTTPError as e :
abort ( e . code )
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
soup = BeautifulSoup ( data . read ( ) . decode ( ) , " html.parser " )
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
title = soup . select ( ' meta[property= " og:title " ] ' ) [ 0 ] . get ( " content " )
2023-06-01 21:25:13 +00:00
body = soup . select ( " div#contest-wrapper " ) [ 0 ]
img = proxy ( body . select ( " div#contest-masthead img " ) [ 0 ] . get ( " src " ) )
2023-06-02 14:02:40 +00:00
entry_count = body . select ( " li.entries-nav-btn " ) [ 0 ] . text . split ( " " ) [ 0 ]
prizes = body . select ( " li.prizes-nav-btn " ) [ 0 ] . text . split ( " " ) [ 0 ]
2023-06-01 21:25:13 +00:00
info = body . select ( " div.contest-body-column-left " ) [ 0 ]
info . select ( " div#site-announcements-page " ) [ 0 ] . decompose ( )
info . select ( " h3 " ) [ 0 ] . decompose ( )
info . select ( " div#contest-body-nav " ) [ 0 ] . decompose ( )
2024-01-16 16:13:59 +00:00
info = str ( info ) . replace ( " https://www.instructables.com " , " / " )
2023-06-01 21:25:13 +00:00
entries = body . select ( " span.contest-entity-count " ) [ 0 ] . text
entry_list = [ ]
for entry in body . select ( " div.contest-entries-list div.contest-entries-list-ible " ) :
link = entry . a [ " href " ]
entry_img = proxy ( entry . select ( " a noscript img " ) [ 0 ] . get ( " src " ) )
entry_title = entry . select ( " a.ible-title " ) [ 0 ] . text
author = entry . select ( " div span.ible-author a " ) [ 0 ] . text
author_link = entry . select ( " div span.ible-author a " ) [ 0 ] . get ( " href " )
channel = entry . select ( " div span.ible-channel a " ) [ 0 ] . text
channel_link = entry . select ( " div span.ible-channel a " ) [ 0 ] . get ( " href " )
views = entry . select ( " .ible-views " ) [ 0 ] . text
2023-06-02 14:02:40 +00:00
entry_list . append (
2024-01-17 15:43:51 +00:00
{
" link " : link ,
" entry_img " : entry_img ,
" entry_title " : entry_title ,
" author " : author ,
" author_link " : author_link ,
" channel " : channel ,
" channel_link " : channel_link ,
" views " : views ,
}
2023-06-02 14:02:40 +00:00
)
return render_template (
2024-01-17 15:43:51 +00:00
" contest.html " ,
title = title ,
img = img ,
entry_count = entry_count ,
prizes = prizes ,
info = info ,
entry_list = entry_list ,
2023-06-02 14:02:40 +00:00
)
@app.route ( " /contest/ " )
2023-06-01 21:25:13 +00:00
def route_contests ( ) :
2024-01-16 16:13:59 +00:00
try :
data = urlopen ( " https://www.instructables.com/contest/ " )
except HTTPError as e :
abort ( e . code )
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
soup = BeautifulSoup ( data . read ( ) . decode ( ) , " html.parser " )
2023-06-01 21:25:13 +00:00
contest_count = str ( soup . select ( " p.contest-count " ) [ 0 ] )
contests = [ ]
for contest in soup . select ( " div#cur-contests div.row-fluid div.contest-banner " ) :
link = contest . select ( " div.contest-banner-inner a " ) [ 0 ] . get ( " href " )
2023-06-02 14:02:40 +00:00
img = proxy ( contest . select ( " div.contest-banner-inner a img " ) [ 0 ] . get ( " src " ) )
alt = contest . select ( " div.contest-banner-inner a img " ) [ 0 ] . get ( " alt " )
2023-06-01 21:25:13 +00:00
deadline = contest . select ( " span.contest-meta-deadline " ) [ 0 ] . get ( " data-deadline " )
prizes = contest . select ( " span.contest-meta-count " ) [ 0 ] . text
entries = contest . select ( " span.contest-meta-count " ) [ 1 ] . text
2024-01-17 15:43:51 +00:00
contests . append (
{
" link " : link ,
" img " : img ,
" alt " : alt ,
" deadline " : deadline ,
" prizes " : prizes ,
" entries " : entries ,
}
)
2023-06-01 21:25:13 +00:00
closed = [ ]
for display in soup . select ( " div.contest-winner-display " ) :
link = display . select ( " div.contest-banner-inner a " ) [ 0 ] . get ( " href " )
img = proxy ( display . select ( " div.contest-banner-inner a img " ) [ 0 ] . get ( " src " ) )
alt = display . select ( " div.contest-banner-inner a img " ) [ 0 ] . get ( " alt " )
featured_items = [ ]
for featured_item in display . select ( " ul.featured-items li " ) :
item_link = featured_item . select ( " div.ible-thumb a " ) [ 0 ] . get ( " href " )
item_img = proxy ( featured_item . select ( " div.ible-thumb a img " ) [ 0 ] . get ( " src " ) )
item_title = featured_item . select ( " a.title " ) [ 0 ] . text
item_author = featured_item . select ( " a.author " ) [ 0 ] . text
item_author_link = featured_item . select ( " a.author " ) [ 0 ] . get ( " href " )
2023-06-02 14:02:40 +00:00
featured_items . append (
2024-01-17 15:43:51 +00:00
{
" link " : item_link ,
" img " : item_img ,
" title " : item_title ,
" author " : item_author ,
" author_link " : item_author_link ,
}
2023-06-02 14:02:40 +00:00
)
2024-01-17 15:43:51 +00:00
closed . append (
{ " link " : link , " img " : img , " alt " : alt , " featured_items " : featured_items }
)
2023-06-01 21:25:13 +00:00
2024-01-17 15:43:51 +00:00
return render_template (
" contests.html " ,
title = " Contests " ,
contest_count = contest_count ,
contests = contests ,
closed = closed ,
)
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /<category>/<channel>/projects/ " )
2023-06-01 21:25:13 +00:00
def route_channel_projects ( category , channel ) :
return project_list ( f " / { category } / { channel } /projects/ " , channel . title ( ) )
2023-06-02 14:02:40 +00:00
@app.route ( " /<category>/<channel>/projects/<sort>/ " )
2023-06-01 21:25:13 +00:00
def route_channel_projects_sort ( category , channel , sort ) :
2023-06-02 14:02:40 +00:00
return project_list (
f " / { category } / { channel } /projects/ { sort } " ,
channel . title ( ) ,
" Sorted by " + sort . title ( ) ,
)
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /<category>/projects/ " )
2023-06-01 21:25:13 +00:00
def route_category_projects ( category ) :
return project_list ( f " / { category } /projects/ " , category . title ( ) )
2023-06-02 14:02:40 +00:00
@app.route ( " /<category>/projects/<sort>/ " )
2023-06-01 21:25:13 +00:00
def route_category_projects_sort ( category , sort ) :
2023-06-02 14:02:40 +00:00
return project_list (
f " / { category } /projects/ { sort } " , category . title ( ) , " Sorted by " + sort . title ( )
)
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /projects/ " )
2023-06-01 21:25:13 +00:00
def route_projects ( ) :
2023-06-02 14:02:40 +00:00
return project_list ( " /projects/ " , " " )
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /search " )
2023-06-01 21:25:13 +00:00
def route_search ( ) :
2024-01-17 15:43:51 +00:00
# TODO: Fix this (using search function)
2023-06-02 14:02:40 +00:00
return project_list ( " /search/?q= " + request . args [ " q " ] + " &projects=all " , " Search " )
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /projects/<sort>/ " )
2023-06-01 21:25:13 +00:00
def route_projects_sort ( sort ) :
2023-06-02 14:02:40 +00:00
return project_list ( f " /projects/ { sort } " , " " , " Sorted by " + sort . title ( ) )
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /circuits/ " )
2023-06-01 21:25:13 +00:00
def route_circuits ( ) :
return category_page ( " /circuits/ " , " Circuits " )
2023-06-02 14:02:40 +00:00
@app.route ( " /workshop/ " )
2023-06-01 21:25:13 +00:00
def route_workshop ( ) :
return category_page ( " /workshop/ " , " Workshop " )
2023-06-02 14:02:40 +00:00
@app.route ( " /craft/ " )
2023-06-01 21:25:13 +00:00
def route_craft ( ) :
return category_page ( " /craft/ " , " Craft " )
2023-06-02 14:02:40 +00:00
@app.route ( " /cooking/ " )
2023-06-01 21:25:13 +00:00
def route_cooking ( ) :
return category_page ( " /cooking/ " , " Cooking " )
2023-06-02 14:02:40 +00:00
@app.route ( " /living/ " )
2023-06-01 21:25:13 +00:00
def route_living ( ) :
return category_page ( " /living/ " , " Living " )
2023-06-02 14:02:40 +00:00
@app.route ( " /outside/ " )
2023-06-01 21:25:13 +00:00
def route_outside ( ) :
return category_page ( " /outside/ " , " Outside " )
2023-06-02 14:02:40 +00:00
@app.route ( " /teachers/ " )
2023-06-01 21:25:13 +00:00
def route_teachers ( ) :
return category_page ( " /teachers/ " , " Teachers " , True )
2023-06-02 14:02:40 +00:00
@app.route ( " /sitemap/projects/<category>/<subcategory> " )
2023-06-01 21:25:13 +00:00
def route_sitemap_circuits ( category , subcategory ) :
2023-06-02 14:02:40 +00:00
return category_page (
" / " + category + " / " + subcategory , subcategory + " - " + category
)
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /member/<member>/instructables/ " )
2023-06-01 21:25:13 +00:00
def route_member_instructables ( member ) :
2024-01-16 16:13:59 +00:00
try :
data = urlopen ( f " https://www.instructables.com/member/ { member } /instructables/ " )
except HTTPError as e :
abort ( e . code )
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
soup = BeautifulSoup ( data . read ( ) . decode ( ) , " html.parser " )
2023-06-01 21:25:13 +00:00
header = soup . select ( " .profile-header.profile-header-social " ) [ 0 ]
header_content = member_header ( header )
ibles = soup . select ( " ul.ible-list-items " ) [ 0 ]
ible_list = [ ]
for ible in ibles . select ( " li " ) :
link = ible . select ( " div.thumbnail-image " ) [ 0 ] . a . get ( " href " )
img = proxy ( ible . select ( " div.thumbnail-image a noscript img " ) [ 0 ] . get ( " src " ) )
title = ible . select ( " div.caption-inner a.title " ) [ 0 ] . text
stats = ible . select ( " div.ible-stats-right-col " ) [ 0 ]
views = 0
if stats . select ( " span.ible-views " ) != [ ] :
views = stats . select ( " span.ible-views " ) [ 0 ] . text
favorites = 0
if stats . select ( " span.ible-favorites " ) != [ ] :
favorites = stats . select ( " span.ible-favorites " ) [ 0 ] . text
2024-01-17 15:43:51 +00:00
ible_list . append (
{
" link " : link ,
" img " : img ,
" title " : title ,
" views " : views ,
" favorites " : favorites ,
}
)
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
return render_template (
2024-01-17 15:43:51 +00:00
" member-instructables.html " ,
title = f " { header_content [ ' title ' ] } ' s Instructables " ,
header_content = header_content ,
ibles = ible_list ,
2023-06-02 14:02:40 +00:00
)
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /member/<member>/ " )
2023-06-01 21:25:13 +00:00
def route_member ( member ) :
headers = {
2023-06-02 14:02:40 +00:00
" User-Agent " : " Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0 "
2023-06-01 21:25:13 +00:00
}
2024-01-16 16:13:59 +00:00
request = Request (
2023-06-02 14:02:40 +00:00
f " https://www.instructables.com/member/ { member } / " , headers = headers
)
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
try :
data = urlopen ( request )
except HTTPError as e :
abort ( e . code )
soup = BeautifulSoup ( data . read ( ) . decode ( ) , " html.parser " )
2023-06-01 21:25:13 +00:00
header_content = member_header ( soup )
body = soup . select ( " div.member-profile-body " ) [ 0 ]
ible_list = body . select ( " .boxed-content.promoted-content " )
ible_list_title = " "
ibles = [ ]
if ible_list != [ ] :
ible_list = ible_list [ 0 ]
ible_list_title = ible_list . select ( " h2.module-title " ) [ 0 ] . text
for ible in ible_list . select ( " ul.promoted-items li " ) :
ible_title = ible . get ( " data-title " )
ible_link = ible . select ( " div.image-wrapper " ) [ 0 ] . a . get ( " href " )
ible_img = proxy ( ible . select ( " div.image-wrapper a img " ) [ 0 ] . get ( " src " ) )
2024-01-17 15:43:51 +00:00
ibles . append ( { " title " : ible_title , " link " : ible_link , " img " : ible_img } )
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
ach_list = body . select (
" div.two-col-section div.right-col-section.centered-sidebar div.boxed-content.about-me "
)
2023-06-01 21:25:13 +00:00
ach_list_title = " "
achs = [ ]
if len ( ach_list ) > 1 :
ach_list = ach_list [ 1 ]
ach_list_title = ach_list . select ( " h2.module-title " ) [ 0 ] . text
2023-06-02 14:02:40 +00:00
for ach in ach_list . select (
" div.achievements-section.main-achievements.contest-achievements div.achievement-item:not(.two-column-filler) "
) :
ach_title = ach . select ( " div.achievement-info span.achievement-title " ) [
0
] . text
ach_desc = ach . select ( " div.achievement-info span.achievement-description " ) [
0
] . text
2023-06-01 21:25:13 +00:00
achs . append ( [ ach_title , ach_desc ] )
2023-06-02 14:02:40 +00:00
return render_template (
" member.html " ,
2024-01-17 15:43:51 +00:00
title = header_content [ " title " ] + " ' s Profile " ,
header_content = header_content ,
ible_list_title = ible_list_title ,
ibles = ibles ,
ach_list_title = ach_list_title ,
achs = achs ,
2023-06-02 14:02:40 +00:00
)
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /<article>/ " )
2023-06-01 21:25:13 +00:00
def route_article ( article ) :
2024-01-16 16:13:59 +00:00
try :
data = urlopen ( f " https://www.instructables.com/ { article } / " )
except HTTPError as e :
abort ( e . code )
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
soup = BeautifulSoup ( data . read ( ) . decode ( ) , " html.parser " )
2023-06-01 21:25:13 +00:00
try :
header = soup . select ( " header " )
if len ( header ) < 2 and soup . select ( " title " ) [ 0 ] . text . contains ( " Pending Review " ) :
2024-01-17 15:43:51 +00:00
return render_template ( " article-review.html " , title = " Pending Review " )
2023-06-01 21:25:13 +00:00
else :
header = header [ 1 ]
title = header . find ( " h1 " ) . text
byline = header . select ( " div.sub-header div.header-byline " ) [ 0 ]
author = byline . select ( " a " ) [ 0 ] . text
author_link = byline . select ( " a " ) [ 0 ] . get ( " href " )
category = byline . select ( " a " ) [ 1 ] . text
category_link = byline . select ( " a " ) [ 1 ] . get ( " href " )
channel = byline . select ( " a " ) [ 2 ] . text
channel_link = byline . select ( " a " ) [ 2 ] . get ( " href " )
stats = header . select ( " div.sub-header div.header-stats " ) [ 0 ]
views = stats . select ( " .view-count " ) [ 0 ] . text
favorites = 0
if stats . select ( " .favorite-count " ) != [ ] :
favorites = stats . select ( " .favorite-count " ) [ 0 ] . text
if soup . select ( " div.article-body " ) != [ ] :
## Instructables
body = soup . select ( " div.article-body " ) [ 0 ]
steps = [ ]
for step in body . select ( " section.step " ) :
2024-01-17 15:43:51 +00:00
print ( step )
2023-06-01 21:25:13 +00:00
step_title = step . select ( " h2 " ) [ 0 ] . text
step_imgs = [ ]
2024-01-17 15:43:51 +00:00
# TODO: Handle download links
for img in step . select ( " img " ) :
step_imgs . append (
{ " src " : proxy ( img . get ( " src " ) ) , " alt " : img . get ( " alt " ) }
)
2023-06-02 14:02:40 +00:00
2023-06-01 21:25:13 +00:00
step_videos = [ ]
for img in step . select ( " video " ) :
step_videos . append ( [ proxy ( img . get ( " src " ) ) ] )
step_text = str ( step . select ( " div.step-body " ) [ 0 ] )
2023-06-02 14:02:40 +00:00
step_text = step_text . replace (
" https://content.instructables.com " ,
2024-01-16 16:13:59 +00:00
" /proxy/?url=https://content.instructables.com " ,
2023-06-02 14:02:40 +00:00
)
2024-01-17 15:43:51 +00:00
steps . append (
{
" title " : step_title ,
" imgs " : step_imgs ,
" text " : step_text ,
" videos " : step_videos ,
}
)
2023-06-01 21:25:13 +00:00
comments_list = [ ]
comment_count = 0
2024-01-17 15:43:51 +00:00
# TODO: Fix comments
2023-06-01 21:25:13 +00:00
# comments = body.select("section.discussion")[0]
# comment_count = comments.select("h2")[0].text
# comment_list = comments.select("div.posts")
# if comment_list != []:
# comment_list = comment_list[0]
# comments_list = []
# replies_used = 0
# for comment in comment_list.select(".post.js-comment:not(.reply)"):
# comment_votes = comment.select(".votes")[0].text
# comment_author_img_src = proxy(comment.select(".avatar a noscript img")[0].get("src"))
# comment_author_img_alt = comment.select(".avatar a noscript img")[0].get("alt")
# comment_author = comment.select(".posted-by a")[0].text
# comment_author_link = comment.select(".posted-by a")[0].get("href")
# comment_date = comment.select(".posted-by p.posted-date")[0].text
# comment_text = comment.select("div.text p")[0]
# comment_reply_count = comment.select("button.js-show-replies")
# if comment_reply_count != []:
# comment_reply_count = comment_reply_count[0].get("data-num-hidden")
# else:
# comment_reply_count = 0
# reply_list = []
# for index, reply in enumerate(comment_list.select(".post.js-comment:not(.reply) ~ .post.js-comment.reply.hide:has(~.post.js-comment:not(.reply))")[replies_used:int(comment_reply_count) + replies_used]):
# reply_votes = reply.select(".votes")[0].text
# reply_author_img_src = proxy(reply.select(".avatar a noscript img")[0].get("src"))
# reply_author_img_alt = reply.select(".avatar a noscript img")[0].get("alt")
# reply_author = reply.select(".posted-by a")[0].text
# reply_author_link = reply.select(".posted-by a")[0].get("href")
# reply_date = reply.select(".posted-by p.posted-date")[0].text
# reply_text = reply.select("div.text p")[0]
# reply_list.append([reply_votes, reply_author_img_src, reply_author_img_alt, reply_author, reply_author_link, reply_date, reply_text])
# replies_used += 1
# comments_list.append([comment_votes, comment_author_img_src, comment_author_img_alt, comment_author, comment_author_link, comment_date, comment_text, comment_reply_count, reply_list])
2023-06-02 14:02:40 +00:00
return render_template (
" article.html " ,
2024-01-17 15:43:51 +00:00
title = title ,
author = author ,
author_link = author_link ,
category = category ,
category_link = category_link ,
channel = channel ,
channel_link = channel_link ,
views = views ,
favorites = favorites ,
steps = steps ,
comment_count = comment_count ,
comments_list = comments_list ,
2023-06-02 14:02:40 +00:00
enumerate = enumerate ,
)
2023-06-01 21:25:13 +00:00
else :
## Collections
thumbnails = [ ]
for thumbnail in soup . select ( " ul#thumbnails-list li " ) :
2023-06-02 14:02:40 +00:00
text = (
link
) = (
img
) = (
thumbnail_title
) = (
thumbnail_author
) = (
thumbnail_author_link
) = thumbnail_channel = thumbnail_channel_link = " "
2023-06-01 21:25:13 +00:00
if thumbnail . select ( " div.thumbnail > p " ) != [ ] :
text = thumbnail . select ( " div.thumbnail > p " ) [ 0 ]
if thumbnail . select ( " div.thumbnail div.thumbnail-image " ) :
2023-06-02 14:02:40 +00:00
link = thumbnail . select ( " div.thumbnail div.thumbnail-image a " ) [
0
] . get ( " href " )
img = proxy (
thumbnail . select ( " div.thumbnail div.thumbnail-image a img " ) [
0
] . get ( " src " )
)
thumbnail_title = thumbnail . select (
" div.thumbnail div.thumbnail-info h3.title a "
) [ 0 ] . text
thumbnail_author = thumbnail . select (
" div.thumbnail div.thumbnail-info span.author a "
) [ 0 ] . text
thumbnail_author_link = thumbnail . select (
" div.thumbnail div.thumbnail-info span.author a "
) [ 0 ] . get ( " href " )
thumbnail_channel = thumbnail . select (
" div.thumbnail div.thumbnail-info span.origin a "
) [ 0 ] . text
thumbnail_channel_link = thumbnail . select (
" div.thumbnail div.thumbnail-info span.origin a "
) [ 0 ] . get ( " href " )
thumbnails . append (
2024-01-17 15:43:51 +00:00
{
" text " : text ,
" link " : link ,
" img " : img ,
" title " : thumbnail_title ,
" author " : thumbnail_author ,
" author_link " : thumbnail_author_link ,
" channel " : thumbnail_channel ,
" channel_link " : thumbnail_channel_link ,
}
2023-06-02 14:02:40 +00:00
)
return render_template (
" collection.html " ,
2024-01-17 15:43:51 +00:00
title = title ,
author = author ,
author_link = author_link ,
category = category ,
category_link = category_link ,
channel = channel ,
channel_link = channel_link ,
views = views ,
favorites = favorites ,
thumbnails = thumbnails ,
2023-06-02 14:02:40 +00:00
)
2023-06-01 21:25:13 +00:00
except Exception :
print_exc ( )
2023-07-19 06:26:45 +00:00
raise InternalServerError ( )
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /<category>/<channel>/ " )
2023-06-01 21:25:13 +00:00
def route_channel_redirect ( category , channel ) :
2024-01-16 16:13:59 +00:00
# TODO: Just check if the channel exists
2023-06-02 14:02:40 +00:00
if (
category == " circuits "
or category == " workshop "
or category == " craft "
or category == " cooking "
or category == " living "
or category == " outside "
or category == " teachers "
) :
2023-06-01 21:25:13 +00:00
return redirect ( f " / { category } / { channel } /projects/ " , 307 )
else :
2023-07-19 06:26:45 +00:00
raise NotFound ( )
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " / " )
2023-06-01 21:25:13 +00:00
def route_explore ( ) :
2024-01-16 16:13:59 +00:00
try :
data = urlopen ( " https://www.instructables.com/ " )
except HTTPError as e :
abort ( e . code )
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
soup = BeautifulSoup ( data . read ( ) . decode ( ) , " html.parser " )
2023-06-01 21:25:13 +00:00
explore = soup . select ( " .home-content-explore-wrap " ) [ 0 ]
title = explore . select ( " h2 " ) [ 0 ] . text
2023-06-02 14:02:40 +00:00
circuits = explore_lists (
explore . select ( " .home-content-explore-category-circuits " ) [ 0 ]
)
workshop = explore_lists (
explore . select ( " .home-content-explore-category-workshop " ) [ 0 ]
)
2023-06-01 21:25:13 +00:00
craft = explore_lists ( explore . select ( " .home-content-explore-category-craft " ) [ 0 ] )
cooking = explore_lists ( explore . select ( " .home-content-explore-category-cooking " ) [ 0 ] )
living = explore_lists ( explore . select ( " .home-content-explore-category-living " ) [ 0 ] )
outside = explore_lists ( explore . select ( " .home-content-explore-category-outside " ) [ 0 ] )
2023-06-02 14:02:40 +00:00
teachers = explore_lists (
explore . select ( " .home-content-explore-category-teachers " ) [ 0 ]
)
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
return render_template (
" index.html " ,
2024-01-17 15:43:51 +00:00
title = title ,
sections = [
( " Circuits " , " /circuits " , circuits ) ,
( " Workshop " , " /workshop " , workshop ) ,
( " Craft " , " /craft " , craft ) ,
( " Cooking " , " /cooking " , cooking ) ,
( " Living " , " /living " , living ) ,
( " Outside " , " /outside " , outside ) ,
( " Teachers " , " /teachers " , teachers ) ,
] ,
2023-06-02 14:02:40 +00:00
)
2023-06-01 21:25:13 +00:00
2023-06-02 14:02:40 +00:00
@app.route ( " /proxy/ " )
2023-06-01 21:25:13 +00:00
def route_proxy ( ) :
url = request . args . get ( " url " )
if url != None :
2023-06-02 14:02:40 +00:00
if url . startswith ( " https://cdn.instructables.com/ " ) or url . startswith (
" https://content.instructables.com/ "
) :
2024-01-16 16:13:59 +00:00
try :
data = urlopen ( unquote ( url ) )
except HTTPError as e :
abort ( e . code )
return Response ( data . read ( ) , content_type = data . headers [ " content-type " ] )
2023-06-01 21:25:13 +00:00
else :
2023-07-19 06:26:45 +00:00
raise BadRequest ( )
2023-06-01 21:25:13 +00:00
else :
2023-07-19 06:26:45 +00:00
raise BadRequest ( )
2023-06-01 21:25:13 +00:00
2024-01-16 16:13:59 +00:00
2023-06-03 22:31:55 +00:00
@app.route ( " /privacypolicy/ " )
def privacypolicy ( ) :
2024-01-17 15:43:51 +00:00
content = " No privacy policy found. "
try :
with ( pathlib . Path ( __file__ ) . parent / " privacy.txt " ) . open ( ) as f :
content = f . read ( )
except :
pass
return render_template (
" privacypolicy.html " , title = " Privacy Policy " , content = content
)
2023-06-03 22:31:55 +00:00
2024-01-16 16:13:59 +00:00
2023-06-01 21:25:13 +00:00
@app.errorhandler ( 404 )
def not_found ( e ) :
return render_template ( " 404.html " )
2024-01-16 16:13:59 +00:00
2023-07-19 06:26:45 +00:00
@app.errorhandler ( 400 )
def bad_request ( e ) :
return render_template ( " 400.html " )
2024-01-16 16:13:59 +00:00
2023-07-19 06:26:45 +00:00
@app.errorhandler ( 429 )
def too_many_requests ( e ) :
return render_template ( " 429.html " )
2024-01-16 16:13:59 +00:00
2023-07-19 06:26:45 +00:00
@app.errorhandler ( 500 )
def internal_server_error ( e ) :
return render_template ( " 500.html " )
2024-01-16 16:13:59 +00:00
if __name__ == " __main__ " :
2023-06-02 14:02:40 +00:00
app . run ( port = args . port , host = args . listen_host , debug = debugmode )