Browse Source

feat: add 404 page with words similar to search

Zubarev Grigoriy 7 months ago
parent
commit
b8bea88d78

+ 4 - 1
README.md

@@ -13,12 +13,15 @@ Privacy-respecting, NoJS-supporting Urban Dictionary frontend.
 
 ## ✨ Features
 
+Frontend supports all Urban Dictionary features and has endpoint-parity with it.
+Available features include:
+
 - Word definitions
 - Author pages
 - Homepage with words of the day
 - Random word definitions
+- 404 page with words similar to search
 - Pagination
-- Matches Urban Dictionary's endpoints for features listed above
 
 ## 🚀 Deployment
 

+ 22 - 3
src/rural_dict/__main__.py

@@ -44,14 +44,33 @@ async def catch_all(response: Request):
         f"{response.url.path}{f'?{response.url.query}' if response.url.query else ''}"
     )
     url = f"https://www.urbandictionary.com{path_without_host}"
+    term = response.query_params.get("term")
 
     async with session.get(url) as dict_response:
         if dict_response.history:
             return RedirectResponse(str(dict_response.url.relative()), status_code=301)
         html = await dict_response.text()
+        parser = HTMLParser(html)
+        if dict_response.status != 200:
+            similar_words = None
+            if (try_this := parser.css_first("div.try-these")) is not None:
+                similar_words = [remove_classes(word).html for word in try_this.css("li a")]
+            return templates.TemplateResponse(
+                "404.html",
+                {
+                    "request": response,
+                    "similar_words": similar_words,
+                    "term": term,
+                    "site_title": f"Rural Dictionary: {term}",
+                    "site_description": (
+                        "View on Rural Dictionary, an alternative private "
+                        "frontend to Urban Dictionary."
+                    ),
+                },
+                status_code=404,
+            )
 
     results = []
-    parser = HTMLParser(html)
     definitions = parser.css("div[data-defid]")
     try:
         thumbs_api_url = (
@@ -61,7 +80,7 @@ async def catch_all(response: Request):
         async with session.get(thumbs_api_url) as thumbs_response:
             thumbs_json = await thumbs_response.json()
             thumbs_data = {el["defid"]: el for el in thumbs_json["thumbs"]}
-    except (KeyError, JSONDecodeError):
+    except (KeyError, JSONDecodeError, TimeoutError):
         thumbs_data = {}
 
     site_description = None
@@ -85,7 +104,7 @@ async def catch_all(response: Request):
         pagination.attrs["class"] = "pagination"  # pyright: ignore [reportIndexIssue]
         pagination = pagination.html
 
-    term = response.query_params.get("term", results[0][1])
+    term = term or results[0][1]
     site_title = "Rural Dictionary"
     match response.url.path:
         case "/":

+ 12 - 0
src/rural_dict/templates/404.html

@@ -0,0 +1,12 @@
+{% extends "base.html" %} {% block content %}
+<div style="text-align: center">
+  <h2>Definition not found: {{ term }}</h2>
+  {% if similar_words %}
+  {% for word in similar_words %}
+  <h3 class="underline-links">{{ word | safe }}</h3>
+  {% endfor %}
+  {% else %}
+  <p>There are no similar words. Try correcting your search.</p>
+  {% endif %}
+</div>
+{% endblock %}

+ 45 - 0
src/rural_dict/templates/base.html

@@ -0,0 +1,45 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="robots" content="index, follow" />
+    <link rel="stylesheet" href="{{ url_for('static', path='css/main.css') }}" />
+    <link rel="icon" type="image/png" href="{{ url_for('static', path='img/favicon.png') }}" />
+    <title>{{ site_title }}</title>
+    <meta name="description" content="{{ site_description }}" />
+    <!-- The Open Graph Protocol meta tags -->
+    <meta property="og:url" content="{{ request.url }}" />
+    <meta property="og:type" content="website" />
+    <meta property="og:title" content="{{ site_title }}" />
+    <meta property="og:description" content="{{ site_description }}" />
+    <meta property="twitter:domain" content="{{ request.url.hostname }}" />
+    <meta property="twitter:url" content="{{ request.url }}" />
+    <meta name="twitter:title" content="{{ site_title }}" />
+    <meta name="twitter:description" content="{{ site_description }}" />
+  </head>
+  <body>
+    <div style="text-align: center">
+      <a href="/">
+        <img src="{{ url_for('static', path='img/logo.png') }}" alt="logo" />
+      </a>
+      <form id="search" role="search" method="get" action="/define.php">
+        <input
+          autocomplete="off"
+          type="search"
+          id="term"
+          name="term"
+          placeholder="Search"
+          aria-label="Search"
+          autofocus
+        />
+        <button>Go</button>
+      </form>
+      <a href="/random.php">Random</a>
+      <br />
+      <a href="https://git.vern.cc/cobra/rural-dict">Source Code</a>
+    </div>
+    <br />
+    {% block content %}{% endblock %}
+  </body>
+</html>

+ 23 - 63
src/rural_dict/templates/index.html

@@ -1,63 +1,23 @@
-<!doctype html>
-<html lang="en">
-  <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1" />
-    <meta name="robots" content="index, follow" />
-    <link rel="stylesheet" href="{{ url_for('static', path='css/main.css') }}" />
-    <link rel="icon" type="image/png" href="{{ url_for('static', path='img/favicon.png') }}" />
-    <title>{{ site_title }}</title>
-    <meta name="description" content="{{ site_description }}" />
-    <!-- Open Graph meta tags -->
-    <meta property="og:url" content="{{ request.url }}" />
-    <meta property="og:type" content="website" />
-    <meta property="og:title" content="{{ site_title }}" />
-    <meta property="og:description" content="{{ site_description }}" />
-    <meta property="twitter:domain" content="{{ request.url.hostname }}" />
-    <meta property="twitter:url" content="{{ request.url }}" />
-    <meta name="twitter:title" content="{{ site_title }}" />
-    <meta name="twitter:description" content="{{ site_description }}" />
-  </head>
-  <body>
-    <div style="text-align: center">
-      <a href="/">
-        <img src="{{ url_for('static', path='img/logo.png') }}" />
-      </a>
-      <form id="search" role="search" method="get" action="/define.php">
-        <input
-          autocomplete="off"
-          type="search"
-          id="term"
-          name="term"
-          placeholder="Search"
-          aria-label="Search"
-          autofocus
-        />
-        <button>Go</button>
-      </form>
-      <a href="/random.php">Random</a>
-      <br />
-      <a href="https://git.vern.cc/cobra/rural-dict">Source Code</a>
-    </div>
-    <br />
-    {% for definition_id, word, meaning, example, contributor, thumbs_up, thumbs_down in results %}
-    <div data-id="{{ definition_id }}">
-      <a href="/define.php?term={{ word }}">
-        <h2>{{ word }}</h2>
-      </a>
-      <div class="underline-links">
-        <p>{{ meaning|safe }}</p>
-        <p><i>{{ example|safe }}</i></p>
-      </div>
-      <div>{{ contributor|safe }}</div>
-      {% if thumbs_up is not none and thumbs_down is not none %}
-      <p>
-        <span title="thumbs up">👍{{ thumbs_up|safe }}</span>
-        <span title="thumbs down">👎{{ thumbs_down|safe }}</span>
-      </p>
-      {% endif %}
-    </div>
-    <br />
-    {% endfor %} {% if pagination %}{{ pagination|safe }} {% endif %}
-  </body>
-</html>
+{% extends "base.html" %}
+{% block content %}
+{% for definition_id, word, meaning, example, contributor, thumbs_up, thumbs_down in results %}
+<div data-id="{{ definition_id }}">
+  <a href="/define.php?term={{ word }}">
+    <h2>{{ word }}</h2>
+  </a>
+  <div class="underline-links">
+    <p>{{ meaning | safe }}</p>
+    <p><i>{{ example | safe }}</i></p>
+  </div>
+  <div>{{ contributor | safe }}</div>
+  {% if thumbs_up is not none and thumbs_down is not none %}
+  <p>
+    <span title="thumbs up">👍{{ thumbs_up | safe }}</span>
+    <span title="thumbs down">👎{{ thumbs_down | safe }}</span>
+  </p>
+  {% endif %}
+</div>
+<br />
+{% endfor %}
+{% if pagination %}{{ pagination | safe }}{% endif %}
+{% endblock %}