Compare commits

...

10 commits

9 changed files with 258 additions and 104 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
.mypy_cache .mypy_cache
__pycache__

108
main.py
View file

@ -1,70 +1,78 @@
#!/usr/bin/python3 #!/usr/bin/python3
"""Find photos on flickr for Wikipedia articles and contact the photographer."""
import collections import collections
import inspect
import json import json
import sys import sys
import traceback
import typing import typing
from urllib.parse import unquote from urllib.parse import unquote
import flask import flask
import requests import requests
import werkzeug
from werkzeug.debug.tbtools import DebugTraceback
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.debug = True app.debug = False
enwiki = "en.wikipedia.org/wiki/" enwiki = "en.wikipedia.org/wiki/"
@app.errorhandler(werkzeug.exceptions.InternalServerError)
def exception_handler(e: werkzeug.exceptions.InternalServerError) -> tuple[str, int]:
"""Handle exception."""
exec_type, exc_value, current_traceback = sys.exc_info()
assert exc_value
tb = DebugTraceback(exc_value)
summary = tb.render_traceback_html(include_title=False)
exc_lines = "".join(tb._te.format_exception_only())
last_frame = list(traceback.walk_tb(current_traceback))[-1][0]
last_frame_args = inspect.getargs(last_frame.f_code)
return (
flask.render_template(
"show_error.html",
plaintext=tb.render_traceback_text(),
exception=exc_lines,
exception_type=tb._te.exc_type.__name__,
summary=summary,
last_frame=last_frame,
last_frame_args=last_frame_args,
),
500,
)
@app.route("/") @app.route("/")
def start() -> str: def start() -> str:
"""Start form.""" """Start form."""
return flask.render_template("wikipedia_url.html") wikipedia_url = flask.request.args.get("wikipedia")
if not wikipedia_url:
return flask.render_template("combined.html")
@app.route("/flickr")
def flickr_search() -> str:
"""Search flickr."""
wikipedia_url = flask.request.args["wikipedia"]
start = wikipedia_url.find(enwiki) + len(enwiki)
name = unquote(wikipedia_url[start:]).replace("_", " ")
return flask.render_template(
"flickr_search.html", name=name, wikipedia_url=wikipedia_url
)
def get_params(line_iter: collections.abc.Iterable[str]) -> str:
"""Find and return params from flickr profile page."""
look_for = 'params: {"isEditingTestimonial":false,'
return next(line[line.find("{") :] for line in line_iter if look_for in line)
def flickr_usrename_to_nsid(username: str) -> str:
"""Get NSID from flickr username."""
url = f"https://www.flickr.com/people/{username}/"
r = requests.get(url)
params = json.loads(get_params(r.text.splitlines()))
return typing.cast(str, params["nsid"])
@app.route("/message")
def show_message() -> str:
"""Show message."""
flickr_url = flask.request.args["flickr"]
wikipedia_url = flask.request.args["wikipedia"]
start = wikipedia_url.find(enwiki) + len(enwiki) start = wikipedia_url.find(enwiki) + len(enwiki)
wiki_part1 = wikipedia_url[:start]
if len(sys.argv) > 4:
name = sys.argv[4]
else:
wiki_part2 = unquote(wikipedia_url[start:]) wiki_part2 = unquote(wikipedia_url[start:])
name = wiki_part2 name = wiki_part2
if "_(" in name: if "_(" in name:
name = name[: name.find("_(")] name = name[: name.find("_(")]
name = name.replace("_", " ") name = name.replace("_", " ")
flickr_url = flask.request.args.get("flickr")
if not flickr_url:
return flask.render_template(
"combined.html",
name=name,
wikipedia_url=wikipedia_url,
)
wiki_part1 = wikipedia_url[:start]
if "/in/" in flickr_url: if "/in/" in flickr_url:
flickr_url = flickr_url[: flickr_url.find("/in/")] flickr_url = flickr_url[: flickr_url.find("/in/")]
@ -93,9 +101,29 @@ def show_message() -> str:
lines = msg.split("\n\n") lines = msg.split("\n\n")
return flask.render_template( return flask.render_template(
"show_message.html", subject=subject, lines=lines, nsid=nsid "combined.html",
name=name,
wikipedia_url=wikipedia_url,
flickr_url=flickr_url,
subject=subject,
lines=lines,
nsid=nsid,
) )
def get_params(line_iter: collections.abc.Iterable[str]) -> str:
"""Find and return params from flickr profile page."""
look_for = 'params: {"isEditingTestimonial":false,'
return next(line[line.find("{") :] for line in line_iter if look_for in line)
def flickr_usrename_to_nsid(username: str) -> str:
"""Get NSID from flickr username."""
url = f"https://www.flickr.com/people/{username}/"
r = requests.get(url)
params = json.loads(get_params(r.text.splitlines()))
return typing.cast(str, params["nsid"])
if __name__ == "__main__": if __name__ == "__main__":
app.run(host="0.0.0.0") app.run(host="0.0.0.0")

78
static/css/exception.css Normal file
View file

@ -0,0 +1,78 @@
div.debugger { text-align: left; padding: 12px; margin: auto;
background-color: white; }
div.detail { cursor: pointer; }
div.detail p { margin: 0 0 8px 13px; font-size: 14px; white-space: pre-wrap;
font-family: monospace; }
div.explanation { margin: 20px 13px; font-size: 15px; color: #555; }
div.footer { font-size: 13px; text-align: right; margin: 30px 0;
color: #86989B; }
h2 { font-size: 16px; margin: 1.3em 0 0.0 0; padding: 9px;
background-color: #11557C; color: white; }
h2 em, h3 em { font-style: normal; color: #A5D6D9; font-weight: normal; }
div.traceback, div.plain { border: 1px solid #ddd; margin: 0 0 1em 0; padding: 10px; }
div.plain p { margin: 0; }
div.plain textarea,
div.plain pre { margin: 10px 0 0 0; padding: 4px;
background-color: #E8EFF0; border: 1px solid #D3E7E9; }
div.plain textarea { width: 99%; height: 300px; }
div.traceback h3 { font-size: 1em; margin: 0 0 0.8em 0; }
div.traceback ul { list-style: none; margin: 0; padding: 0 0 0 1em; }
div.traceback h4 { font-size: 13px; font-weight: normal; margin: 0.7em 0 0.1em 0; }
div.traceback pre { margin: 0; padding: 5px 0 3px 15px;
background-color: #E8EFF0; border: 1px solid #D3E7E9; }
div.traceback .library .current { background: white; color: #555; }
div.traceback .expanded .current { background: #E8EFF0; color: black; }
div.traceback pre:hover { background-color: #DDECEE; color: black; cursor: pointer; }
div.traceback div.source.expanded pre + pre { border-top: none; }
div.traceback span.ws { display: none; }
div.traceback pre.before, div.traceback pre.after { display: none; background: white; }
div.traceback div.source.expanded pre.before,
div.traceback div.source.expanded pre.after {
display: block;
}
div.traceback div.source.expanded span.ws {
display: inline;
}
div.traceback blockquote { margin: 1em 0 0 0; padding: 0; white-space: pre-line; }
div.traceback img { float: right; padding: 2px; margin: -3px 2px 0 0; display: none; }
div.traceback img:hover { background-color: #ddd; cursor: pointer;
border-color: #BFDDE0; }
div.traceback pre:hover img { display: block; }
div.traceback cite.filename { font-style: normal; color: #3B666B; }
pre.console { border: 1px solid #ccc; background: white!important;
color: black; padding: 5px!important;
margin: 3px 0 0 0!important; cursor: default!important;
max-height: 400px; overflow: auto; }
pre.console form { color: #555; }
pre.console input { background-color: transparent; color: #555;
width: 90%; font-family: 'Consolas', 'Deja Vu Sans Mono',
'Bitstream Vera Sans Mono', monospace; font-size: 14px;
border: none!important; }
span.string { color: #30799B; }
span.number { color: #9C1A1C; }
span.help { color: #3A7734; }
span.object { color: #485F6E; }
span.extended { opacity: 0.5; }
span.extended:hover { opacity: 1; }
a.toggle { text-decoration: none; background-repeat: no-repeat;
background-position: center center;
background-image: url(?__debugger__=yes&cmd=resource&f=more.png); }
a.toggle:hover { background-color: #444; }
a.open { background-image: url(?__debugger__=yes&cmd=resource&f=less.png); }
div.traceback pre, div.console pre {
white-space: pre-wrap; /* css-3 should we be so lucky... */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 ?? */
white-space: -o-pre-wrap; /* Opera 7 ?? */
word-wrap: break-word; /* Internet Explorer 5.5+ */
_white-space: pre; /* IE only hack to re-specify in
addition to word-wrap */
}

View file

@ -1,32 +1,25 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flickr Mail</title> <title>{% block title %}Xanadu{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
{% block style %}
{% endblock %}
</head> </head>
<body> <body>
<div class="container"> {% block content %}
<div class="row"> {% endblock %}
<h1>Enter URLs</h1>
<form action="{{ url_for("show_message") }}">
<div class="mb-3">
<label for="flickr" class="form-label">Flickr URL:</label>
<input type="text" class="form-control" id="flickr" name="flickr" required>
</div>
<div class="mb-3">
<label for="wikipedia" class="form-label">Wikipedia URL:</label>
<input type="text" class="form-control" id="wikipedia" name="wikipedia" required>
</div>
<input type="submit" value="Submit">
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm" crossorigin="anonymous"></script>
{% block scripts %}
{% endblock %}
</body> </body>
</html> </html>

72
templates/combined.html Normal file
View file

@ -0,0 +1,72 @@
{% extends "base.html" %}
{% block title %}Flickr mail{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<h1>Flickr mail</h1>
<form action="{{ url_for(request.endpoint) }}">
<div class="mb-3">
<label for="wikipedia" class="form-label">Wikipedia URL:</label>
<input type="text" class="form-control" id="wikipedia" name="wikipedia" value="{{ wikipedia_url }}" required>
</div>
<input type="submit" value="Submit">
</form>
{% if name %}
<p>Wikipedia article: {{ name }}</p>
<p><a href="https://flickr.com/search/?view_all=1&safe_search=3&text={{ '"' + name + '"' | urlencode }}">Search flickr</a></p>
<form action="{{ url_for(request.endpoint) }}">
<input type="hidden" name="wikipedia" value="{{ wikipedia_url }}"></input>
<div class="mb-3">
<label for="flickr" class="form-label">Flickr URL:</label>
<input type="text" class="form-control" id="flickr" name="flickr" value="{{ flickr_url }}" required>
</div>
<input type="submit" value="Submit">
</form>
{% endif %}
{% if flickr_url %}
<p><a href="https://www.flickr.com/mail/write/?to={{nsid}}">send message</a>
<div><strong>Subject:</strong> {{ subject }} <button class="btn btn-primary" id="copy-subject">copy</button>
<div>
<h3>message
<button class="btn btn-primary" id="copy-message">copy</button>
</h3>
{% for p in lines %}
<p>{{ p }}</p>
{% endfor %}
</div>
{% endif %}
</div>
</div>
{% endblock %}
{% block scripts %}
{% if subject and lines %}
<script>
var copy_subject = document.getElementById("copy-subject");
var copy_message = document.getElementById("copy-message");
var subject = {{ subject | tojson }};
var message = {{ "\n\n".join(lines) | tojson }};
copy_subject.addEventListener("click", function(e) {
navigator.clipboard.writeText(subject);
});
copy_message.addEventListener("click", function(e) {
navigator.clipboard.writeText(message);
});
</script>
{% endif %}
{% endblock %}

View file

@ -1,23 +0,0 @@
{% extends "base.html" %}
{% block title %}Flickr mail{% endblock %}
{% block content %}
<div class="container">
<h1>Flickr mail</h1>
<p>Wikipedia article: {{ name }}</p>
<p><a href="https://flickr.com/search/?view_all=1&safe_search=3&text={{ '"' + name + '"' | urlencode }}">Search flickr</a></p>
<form action="{{ url_for("show_message") }}">
<input type="hidden" name="wikipedia" value="{{ wikipedia_url }}"></input>
<div class="mb-3">
<label for="flickr" class="form-label">Flickr URL:</label>
<input type="text" class="form-control" id="flickr" name="flickr" required>
</div>
<input type="submit" value="Submit">
</form>
</div>
{% endblock %}

View file

@ -21,3 +21,5 @@ Thank you for your consideration, and if you have any questions or require furth
Warm regards, Warm regards,
Edward Edward
edward@4angle.com

23
templates/show_error.html Normal file
View file

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% block style %}
<link rel="stylesheet" href="{{url_for('static', filename='css/exception.css')}}" />
{% endblock %}
{% block content %}
<div class="p-2">
<h1>Software error: {{ exception_type }}</h1>
<div>
<pre>{{ exception }}</pre>
</div>
<h2 class="traceback">Traceback <em>(most recent call last)</em></h2>
{{ summary | safe }}
<p>Error in function "{{ last_frame.f_code.co_name }}": {{ last_frame_args | pprint }}</p>
<pre>{{ last_frame.f_locals | pprint }}</pre>
</div>
{% endblock %}

View file

@ -1,20 +0,0 @@
{% extends "base.html" %}
{% block title %}Flickr mail{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<h1>Enter URLs</h1>
<form action="{{ url_for("flickr_search") }}">
<div class="mb-3">
<label for="wikipedia" class="form-label">Wikipedia URL:</label>
<input type="text" class="form-control" id="wikipedia" name="wikipedia" required>
</div>
<input type="submit" value="Submit">
</form>
</div>
</div>
{% endblock %}