Added UI
This commit is contained in:
parent
6fde26affd
commit
52e8b92f1f
|
@ -0,0 +1,10 @@
|
|||
FROM python:3-alpine
|
||||
|
||||
WORKDIR /usr/src/rural-dict
|
||||
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD [ "python", "./main.py" ]
|
45
README.md
45
README.md
|
@ -1,3 +1,46 @@
|
|||
# country-dictionary
|
||||
# Country Dictionary
|
||||
|
||||
A privacy-respecting Urban Dictionary client in Flask - Mostly based on https://git.vern.cc/cobra/rural-dict
|
||||
|
||||
## Instances
|
||||
|
||||
| **URL** | **Country** | **Ownername** | **Owner Website** | |
|
||||
| ---------------------------------- | ----------- | ------------- | ------------------------ | --- |
|
||||
| https://country-dict.winscloud.net | TH | WinsDominoes | https://winsdominoes.net | |
|
||||
|
||||
## About
|
||||
|
||||
Country Dictionary scrapes _Urban Dictionary_ for data and then displays it in HTML & CSS.
|
||||
|
||||
### Supports
|
||||
|
||||
- Define a word with multiple entries
|
||||
- Pagination
|
||||
- Random list of words
|
||||
- User pages
|
||||
- Urban Dictionary home with words of the day
|
||||
- Matches urban dictionary's endpoints for features listed above
|
||||
|
||||
### Dependencies
|
||||
|
||||
- bs4
|
||||
- requests
|
||||
- waitress
|
||||
- Relatively new version of python
|
||||
|
||||
### Redirection
|
||||
|
||||
Simply replace an _Urban Dictionary_ URL with a Country Dictionary URL from the instance list above.
|
||||
|
||||
```
|
||||
https://urbandictionary.com/define.php?term=eevee
|
||||
|
||||
https://country-dict.winscloud.net/define.php?term=eevee
|
||||
```
|
||||
|
||||
NOTE: More endpoints are supported
|
||||
|
||||
## Contributors
|
||||
|
||||
- https://git.vern.cc/cobra/rural-dict - the original Rural Dictionary project
|
||||
- https://codeberg.org/zortazert - created the initial Urban Dictionary frontend using JavaScript and helped develop Rural Dictionary
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
version: "3"
|
||||
services:
|
||||
rural-dict:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:8080:8080"
|
|
@ -0,0 +1,8 @@
|
|||
[
|
||||
{
|
||||
"clearnet": "https://country-dict.winscloud.net",
|
||||
"country": "TH",
|
||||
"owner_name": "WinsDominoes",
|
||||
"owner_website": "https://winsdominoes.net"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from flask import Flask, render_template, request, redirect
|
||||
import requests
|
||||
import html
|
||||
import re
|
||||
from bs4 import BeautifulSoup
|
||||
from urllib.parse import quote, unquote
|
||||
|
||||
def scrape(url):
|
||||
data = requests.get(url)
|
||||
|
||||
our_path = re.sub(r".*://.*/", "/", request.url)
|
||||
path = re.sub(r".*://.*/", "/", data.url)
|
||||
if our_path != path and \
|
||||
quote(unquote(re.sub("[?&=]", "", our_path))) != re.sub("[?&=]", "", path):
|
||||
# this is bad ^
|
||||
return f"REDIRECT {path}"
|
||||
ret = []
|
||||
soup = BeautifulSoup(data.text, "html.parser")
|
||||
|
||||
defs = [(div, div.get('data-defid')) for div in soup.find_all("div") if div.get('data-defid')]
|
||||
try:
|
||||
thumbs_data = {
|
||||
str(entry['defid']): entry
|
||||
for entry
|
||||
in requests.get(
|
||||
'https://api.urbandictionary.com/v0/uncacheable?ids=' + ','.join(defid for (_, defid) in defs)
|
||||
).json()['thumbs']
|
||||
}
|
||||
except:
|
||||
thumbs_data = {}
|
||||
|
||||
for (definition, defid) in defs:
|
||||
word = definition.select("div div h1 a, div div h2 a")[0].text
|
||||
meaning = definition.find(attrs={"class" : ["break-words meaning mb-4"]}).decode_contents()
|
||||
example = definition.find(attrs={"class" : ["break-words example italic mb-4"]}).decode_contents()
|
||||
contributor = definition.find(attrs={"class" : ["contributor font-bold"]})
|
||||
thumbs_up = thumbs_data.get(defid, {}).get('up')
|
||||
thumbs_down = thumbs_data.get(defid, {}).get('down')
|
||||
ret.append([defid, word, meaning, example, contributor, thumbs_up, thumbs_down])
|
||||
pages = soup.find(attrs={"class" : ["pagination text-xl text-center"]})
|
||||
if pages == None:
|
||||
pages = ""
|
||||
return (ret, pages)
|
||||
|
||||
app = Flask(__name__, template_folder="templates", static_folder="static")
|
||||
|
||||
@app.route('/', defaults={'path': ''})
|
||||
@app.route('/<path:path>')
|
||||
def catch_all(path):
|
||||
scraped = scrape(f"https://urbandictionary.com/{re.sub(r'.*://.*/', '/', request.url)}")
|
||||
if type(scraped) == str and scraped.startswith("REDIRECT"):
|
||||
return redirect(scraped.replace("REDIRECT ", ""), 302)
|
||||
return render_template('index.html', data=scraped)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from waitress import serve
|
||||
serve(app, host="0.0.0.0", port=8089)
|
|
@ -0,0 +1,4 @@
|
|||
beautifulsoup4
|
||||
requests
|
||||
flask
|
||||
waitress
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,28 @@
|
|||
@font-face {
|
||||
font-family: "Roboto";
|
||||
src: url("../fonts/Roboto-Regular.ttf") format("truetype");
|
||||
} /* Safari, Android, iOS */
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: "Roboto" !important;
|
||||
height: 100%;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-right: 1ch;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pagination ul > li {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
padding-left: 1ch;
|
||||
}
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
@ -0,0 +1,19 @@
|
|||
(function () {
|
||||
const htmlElement = document.querySelector("html");
|
||||
if (htmlElement.getAttribute("data-bs-theme") === "auto") {
|
||||
function updateTheme() {
|
||||
document
|
||||
.querySelector("html")
|
||||
.setAttribute(
|
||||
"data-bs-theme",
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light"
|
||||
);
|
||||
}
|
||||
window
|
||||
.matchMedia("(prefers-color-scheme: dark)")
|
||||
.addEventListener("change", updateTheme);
|
||||
updateTheme();
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,71 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="mdui-theme-auto" data-bs-theme="auto">
|
||||
<head>
|
||||
<title>Country Dictionary</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<script src="{{ url_for('static', filename='js/theme.js') }}"></script>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}" />
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap-icons.min.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}" />
|
||||
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/favicon.png') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div id="logoContainer" class="mt-5">
|
||||
<a href="/" class="text-light-emphasis">
|
||||
<h1 class="display-1 fw-bold">Country Dictionary</h1>
|
||||
</a>
|
||||
<p>A fork of <a href="https://git.vern.cc/cobra/rural-dict">Rural Dictionary</a></p>
|
||||
</div>
|
||||
<div class="field-text">
|
||||
<form id="search" role="search" method="get" action="/define.php">
|
||||
<div class="input-group mb-3">
|
||||
<input type="search" id="term" name="term" placeholder="Search" class="form-control" autofocus>
|
||||
<button class="btn btn-outline-secondary">Go</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row row-cols-auto gap-2 mb-3 justify-content-center">
|
||||
<a href=/random.php>
|
||||
<button class="btn btn-danger">
|
||||
Random
|
||||
</button>
|
||||
</a>
|
||||
<a href="https://git.winscloud.net/winsdominoes/country-dict">
|
||||
<button class="btn btn-warning">
|
||||
Source Code
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
{% for defid, word, definition, example, author, thumbs_up, thumbs_down in data[0] %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="{{ defid }}">
|
||||
<a href="/define.php?term={{ word }}">
|
||||
<h2>{{ word }}</h2>
|
||||
</a>
|
||||
<p>{{ definition|safe }}</p>
|
||||
<p><i>{{ example|safe }}</i></p>
|
||||
<p>{{ author|safe }}</p>
|
||||
{% if thumbs_up and thumbs_down %}
|
||||
<div class="d-grid gap-2 d-md-block">
|
||||
<button class="btn btn-outline-secondary disabled">
|
||||
<i class="bi bi-hand-thumbs-up-fill"></i>
|
||||
{{ thumbs_up|safe }}
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary disabled">
|
||||
<i class="bi bi-hand-thumbs-down-fill"></i>
|
||||
{{ thumbs_down|safe }}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="d-flex justify-content-center">
|
||||
{{ data[1]|safe }}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue