views.py (8507B)
1 import math 2 import os 3 from datetime import datetime 4 from urllib.parse import quote, unquote 5 6 import jinja2 7 from jetforce import Request, Response, Status, JetforceApplication 8 9 from . import constants 10 from .models import ( 11 compute_verbose, 12 compute_requested_results_page, 13 GUS, 14 process_seed_request, 15 ) 16 from gus.excludes import EXCLUDED_URL_PREFIXES, EXCLUDED_URL_PATHS 17 18 TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "templates") 19 20 template_env = jinja2.Environment( 21 loader=jinja2.FileSystemLoader(TEMPLATE_DIR), 22 undefined=jinja2.StrictUndefined, 23 trim_blocks=True, 24 lstrip_blocks=True, 25 ) 26 27 28 def datetimeformat(value, format="%Y-%m-%d"): 29 return value.strftime(format) 30 31 32 template_env.filters["datetimeformat"] = datetimeformat 33 34 35 def render_template(name: str, *args, **kwargs) -> str: 36 """ 37 Render a gemini directory using the Jinja2 template engine. 38 """ 39 return template_env.get_template(name).render(*args, **kwargs) 40 41 42 app = JetforceApplication() 43 gus = GUS() 44 45 46 @app.route("/status", strict_trailing_slash=False) 47 def status(request): 48 return Response(Status.SUCCESS, "text/plain", "ok") 49 50 @app.route("/robots.txt", strict_trailing_slash=False) 51 def status(request): 52 return Response(Status.SUCCESS, "text/plain", 53 """User-agent: researcher 54 User-agent: indexer 55 User-agent: archiver 56 Disallow: /v/search 57 Disallow: /search 58 Disallow: /backlinks 59 60 User-agent: * 61 Disallow: /add-seed 62 """) 63 64 @app.route("/favicon.txt", strict_trailing_slash=False) 65 def favicon(request): 66 return Response(Status.SUCCESS, "text/plain", "ðŸ”") 67 68 69 @app.route("/add-seed", strict_trailing_slash=False) 70 def add_seed(request): 71 if request.query: 72 process_seed_request(request.query) 73 body = render_template("add_seed.gmi", seed_url=request.query) 74 return Response(Status.SUCCESS, "text/gemini", body) 75 else: 76 return Response(Status.INPUT, "Gemini URL") 77 78 79 @app.route("/statistics", strict_trailing_slash=False) 80 def statistics(request): 81 body = render_template( 82 "statistics.gmi", 83 statistics=gus.statistics, 84 index_modification_time=gus.statistics["index_modification_time"] 85 ) 86 return Response(Status.SUCCESS, "text/gemini", body) 87 88 89 @app.route("/statistics/historical/overall", strict_trailing_slash=False) 90 def statistics(request): 91 body = render_template( 92 "statistics_historical_overall.gmi", 93 statistics_historical_overall=gus.statistics_historical_overall[::-1], 94 index_modification_time=gus.statistics["index_modification_time"] 95 ) 96 return Response(Status.SUCCESS, "text/gemini", body) 97 98 99 @app.route("/known-hosts", strict_trailing_slash=False) 100 def known_hosts(request): 101 body = render_template( 102 "known_hosts.gmi", 103 known_hosts=gus.hosts, 104 index_modification_time=gus.statistics["index_modification_time"] 105 ) 106 return Response(Status.SUCCESS, "text/gemini", body) 107 108 109 @app.route("/newest-hosts", strict_trailing_slash=False) 110 def newest_hosts(request): 111 body = render_template( 112 "newest_hosts.gmi", 113 newest_hosts=gus.newest_hosts, 114 index_modification_time=gus.statistics["index_modification_time"] 115 ) 116 return Response(Status.SUCCESS, "text/gemini", body) 117 118 119 @app.route("/newest-pages", strict_trailing_slash=False) 120 def newest_pages(request): 121 body = render_template( 122 "newest_pages.gmi", 123 newest_pages=gus.newest_pages, 124 index_modification_time=gus.statistics["index_modification_time"] 125 ) 126 return Response(Status.SUCCESS, "text/gemini", body) 127 128 129 @app.route("/known-feeds", strict_trailing_slash=False) 130 def known_feeds(request): 131 body = render_template( 132 "known_feeds.gmi", 133 known_feeds=gus.feeds, 134 index_modification_time=gus.statistics["index_modification_time"] 135 ) 136 137 return Response(Status.SUCCESS, "text/gemini", body) 138 139 140 @app.route("", strict_trailing_slash=False) 141 def index(request): 142 body = render_template( 143 "index.gmi", 144 index_modification_time=gus.statistics["index_modification_time"] 145 ) 146 return Response(Status.SUCCESS, "text/gemini", body) 147 148 149 @app.route("/about", strict_trailing_slash=False) 150 def index(request): 151 body = render_template( 152 "about.gmi", 153 index_modification_time=gus.statistics["index_modification_time"] 154 ) 155 return Response(Status.SUCCESS, "text/gemini", body) 156 157 158 @app.route("/documentation/searching", strict_trailing_slash=False) 159 def documentation_searching(request): 160 body = render_template( 161 "documentation/searching.gmi", 162 index_modification_time=gus.statistics["index_modification_time"] 163 ) 164 return Response(Status.SUCCESS, "text/gemini", body) 165 166 167 @app.route("/documentation/indexing", strict_trailing_slash=False) 168 def documentation_indexing(request): 169 body = render_template( 170 "documentation/indexing.gmi", 171 index_modification_time=gus.statistics["index_modification_time"] 172 ) 173 return Response(Status.SUCCESS, "text/gemini", body) 174 175 176 @app.route("/documentation/filters", strict_trailing_slash=False) 177 def documentation_filters(request): 178 body = render_template( 179 "documentation/filters.gmi", 180 index_modification_time=gus.statistics["index_modification_time"], 181 filters=sorted(EXCLUDED_URL_PREFIXES) 182 ) 183 return Response(Status.SUCCESS, "text/gemini", body) 184 185 186 @app.route("/documentation/backlinks", strict_trailing_slash=False) 187 def documentation_backlinks(request): 188 body = render_template( 189 "documentation/backlinks.gmi", 190 index_modification_time=gus.statistics["index_modification_time"] 191 ) 192 return Response(Status.SUCCESS, "text/gemini", body) 193 194 195 @app.route("/news", strict_trailing_slash=False) 196 def index(request): 197 body = render_template( 198 "news.gmi", 199 index_modification_time=gus.statistics["index_modification_time"] 200 ) 201 return Response(Status.SUCCESS, "text/gemini", body) 202 203 204 @app.route("/v/search(/\d+)?") 205 @app.route("/search(/\d+)?") 206 def search(request): 207 if request.query: 208 verbose = compute_verbose(request.path) 209 requested_page = compute_requested_results_page(request.path) 210 num_results, results = gus.search_index(request.query, requested_page) 211 #if num_results > 0: 212 num_pages = math.ceil(num_results / 10) 213 current_page = min(requested_page, num_pages) 214 if num_results == 0: 215 current_page = 0 216 body = render_template( 217 "search.gmi", 218 query=request.query, 219 quoted_query=quote(request.query), 220 verbose=verbose, 221 num_results=num_results, 222 results=results, 223 current_page=current_page, 224 num_pages=num_pages, 225 index_modification_time=gus.statistics["index_modification_time"] 226 ) 227 #else: 228 # search_suggestions = gus.get_search_suggestions(request.query) 229 # body = render_template( 230 # "search_suggestions.gmi", 231 # query=request.query, 232 # search_suggestions=search_suggestions, 233 # index_modification_time=gus.statistics["index_modification_time"] 234 # ) 235 236 return Response(Status.SUCCESS, "text/gemini", body) 237 else: 238 return Response(Status.INPUT, "Search query") 239 240 241 @app.route("/search/jump/(?P<query>.+)") 242 @app.route("/v/search/jump/(?P<query>.+)") 243 def search_jump(request, query): 244 if request.query: 245 if request.query.isdigit(): 246 jump_page = request.query 247 else: 248 jump_page = 1 249 verbose_segment = "/v" if request.path.startswith("/v") else "" 250 jump_url = f"{verbose_segment}/search/{jump_page}?{query}" 251 return Response(Status.REDIRECT_TEMPORARY, jump_url) 252 else: 253 return Response(Status.INPUT, "Jump to page") 254 255 256 @app.route("/backlinks") 257 def backlinks(request): 258 if request.query: 259 url = unquote(request.query) 260 if not url.startswith("gemini://"): 261 url = "gemini://{}".format(url) 262 internal_backlinks, external_backlinks = gus.get_backlinks(url) 263 body = render_template( 264 "backlinks.gmi", 265 url=url, 266 internal_backlinks=internal_backlinks, 267 external_backlinks=external_backlinks, 268 index_modification_time=gus.statistics["index_modification_time"] 269 ) 270 return Response(Status.SUCCESS, "text/gemini", body) 271 else: 272 return Response(Status.INPUT, "Gemini URL")