Compare commits

...

10 commits

9 changed files with 258 additions and 104 deletions

1
.gitignore vendored
View file

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

108
main.py
View file

@ -1,70 +1,78 @@
#!/usr/bin/python3
"""Find photos on flickr for Wikipedia articles and contact the photographer."""
import collections
import inspect
import json
import sys
import traceback
import typing
from urllib.parse import unquote
import flask
import requests
import werkzeug
from werkzeug.debug.tbtools import DebugTraceback
app = flask.Flask(__name__)
app.debug = True
app.debug = False
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("/")
def start() -> str:
"""Start form."""
return flask.render_template("wikipedia_url.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"]
wikipedia_url = flask.request.args.get("wikipedia")
if not wikipedia_url:
return flask.render_template("combined.html")
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:])
name = wiki_part2
if "_(" in name:
name = name[: name.find("_(")]
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:
flickr_url = flickr_url[: flickr_url.find("/in/")]
@ -93,9 +101,29 @@ def show_message() -> str:
lines = msg.split("\n\n")
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__":
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>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<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">
{% block style %}
{% endblock %}
</head>
<body>
<div class="container">
<div class="row">
<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>
{% block content %}
{% endblock %}
<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>
{% block scripts %}
{% endblock %}
</body>
</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,
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 %}