Compare commits
10 commits
d29285d468
...
adc7cffbd6
Author | SHA1 | Date | |
---|---|---|---|
Edward Betts | adc7cffbd6 | ||
Edward Betts | 1ebed94f5d | ||
Edward Betts | af1640abec | ||
Edward Betts | 60708b5bb7 | ||
Edward Betts | 3c7a81eaf4 | ||
Edward Betts | 0090c3fab6 | ||
Edward Betts | 3f573043a1 | ||
Edward Betts | 5d257055a6 | ||
Edward Betts | 0768cc101c | ||
Edward Betts | 1c6a83083a |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
.mypy_cache
|
||||
__pycache__
|
||||
|
|
110
main.py
110
main.py
|
@ -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]
|
||||
wiki_part2 = unquote(wikipedia_url[start:])
|
||||
|
||||
if len(sys.argv) > 4:
|
||||
name = sys.argv[4]
|
||||
else:
|
||||
wiki_part2 = unquote(wikipedia_url[start:])
|
||||
name = wiki_part2
|
||||
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
78
static/css/exception.css
Normal 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 */
|
||||
}
|
|
@ -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
72
templates/combined.html
Normal 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 %}
|
|
@ -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 %}
|
|
@ -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
23
templates/show_error.html
Normal 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 %}
|
|
@ -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 %}
|
Loading…
Reference in a new issue