diff --git a/add_links/api.py b/add_links/api.py index 05fc7b1..6f57def 100644 --- a/add_links/api.py +++ b/add_links/api.py @@ -1,4 +1,5 @@ import re +import sys import typing import requests @@ -72,18 +73,33 @@ webpage_error = ( ) +def _get_active_session() -> requests.sessions.Session: + """Return OAuth session if one is available in Flask context, else plain session.""" + try: + from flask import g + if hasattr(g, "oauth_session") and g.oauth_session is not None: + return g.oauth_session # type: ignore[return-value] + except RuntimeError: + pass + print("WARNING: using unauthenticated session", file=sys.stderr) + return get_session() + + def api_get(params: StrDict) -> StrDict: """Make call to Wikipedia API.""" - s = get_session() + s = _get_active_session() r = s.get(get_query_url(), params=params) try: ret: StrDict = r.json() except JSONDecodeError: + print(f"API request failed: HTTP {r.status_code}", file=sys.stderr) + print(f"Response body: {r.text!r}", file=sys.stderr) if webpage_error in r.text: raise MediawikiError(webpage_error) - else: - raise MediawikiError("unknown error") + if r.status_code == 429: + raise MediawikiError("Wikipedia rate limit exceeded — wait a moment and try again.") + raise MediawikiError(f"HTTP {r.status_code}: {r.text[:200]!r}") check_for_error(ret) return ret @@ -271,7 +287,7 @@ def call_get_diff(title: str, section_num: int, section_text: str) -> str: "rvdifftotext": section_text.strip(), } - s = get_session() + s = _get_active_session() r = s.post(get_query_url(), data=data) try: ret = r.json() diff --git a/add_links/match.py b/add_links/match.py index 9d00404..a3440e7 100644 --- a/add_links/match.py +++ b/add_links/match.py @@ -78,7 +78,7 @@ re_cite = re.compile( re.I | re.S, ) -re_cite_template_start = re.compile(r"\{\{(?:cite|citation|short description|gli|defn|annotated link|excerpt|main|see)\b", re.I) +re_cite_template_start = re.compile(r"\{\{(?:cite|citation|short description|gli|defn|annotated link|excerpt|main|see|for)\b", re.I) re_no_param_template = re.compile(r"\{\{[^|{}]+\}\}") re_external_link = re.compile(r"\[https?://[^\]]+\]") # Italic text (work titles in bibliographies). Handles apostrophes in content @@ -252,6 +252,14 @@ def add_link(m: re.Match[str], replacement: str, text: str) -> str: if matched_text.startswith("[[") and matched_text.endswith("|"): return m.re.sub(lambda m: f"[[{replacement}|", text, count=1) + split_links = matched_text.find("]] [[") + if split_links > 0 and m.start() >= 2 and text[m.start() - 2 : m.start()] == "[[": + # Match starts inside one link and continues into the next opening link. + # Link only the text from the first link span and leave the second link as-is. + link_dest = replacement.split("|")[0] if "|" in replacement else replacement + visible = matched_text[:split_links] + return text[: m.start() - 2] + f"[[{link_dest}|{visible}]]" + text[m.start() + split_links + 2 :] + inner_bracket = matched_text.find("[[") if inner_bracket > 0: prefix = matched_text[:inner_bracket].rstrip() @@ -551,4 +559,6 @@ def get_diff(q: str, title: str, linkto: str | None) -> dict[str, typing.Any]: ) found["diff"] = call_get_diff(title, found["section_num"], section_text) + if not found["diff"]: + raise NoMatch return found diff --git a/add_links/mediawiki_oauth.py b/add_links/mediawiki_oauth.py index 39aecb0..12ebd58 100644 --- a/add_links/mediawiki_oauth.py +++ b/add_links/mediawiki_oauth.py @@ -1,5 +1,6 @@ """Wikipedia OAuth.""" +import sys import typing import urllib from typing import cast @@ -73,9 +74,8 @@ def api_request(params: typing.Mapping[str, str | int]) -> dict[str, typing.Any] try: return cast(dict[str, typing.Any], r.json()) except Exception: - print("text") - print(r.text) - print("---") + print(f"API request failed: HTTP {r.status_code}", file=sys.stderr) + print(f"Response body: {r.text!r}", file=sys.stderr) raise @@ -99,13 +99,40 @@ def userinfo_call() -> typing.Mapping[str, typing.Any]: return api_request(params) +def get_oauth_session() -> OAuth1Session | None: + """Return an OAuth1Session for the current user, or None if not logged in.""" + if "owner_key" not in session or "owner_secret" not in session: + return None + app = current_app + client_key = app.config["CLIENT_KEY"] + client_secret = app.config["CLIENT_SECRET"] + oauth = OAuth1Session( + client_key, + client_secret=client_secret, + resource_owner_key=session["owner_key"], + resource_owner_secret=session["owner_secret"], + ) + oauth.headers.update({"User-Agent": ua}) + oauth.params = typing.cast( + dict[str, str | int], + {"format": "json", "action": "query", "formatversion": 2}, + ) + return oauth + + def get_username() -> None | str: """Get the username or None if not logged in.""" if "owner_key" not in session: return None # not authorized if "username" not in session: - reply = userinfo_call() + try: + reply = userinfo_call() + except Exception as e: + print(f"get_username failed, clearing session: {e}", file=sys.stderr) + session.pop("owner_key", None) + session.pop("owner_secret", None) + return None if "query" not in reply: return None session["username"] = reply["query"]["userinfo"]["name"] diff --git a/static/css/diff.css b/static/css/diff.css index 65d5ef9..c7009f2 100644 --- a/static/css/diff.css +++ b/static/css/diff.css @@ -5,19 +5,16 @@ span.searchmatch { font-weight: bold; } table.diff,td.diff-otitle,td.diff-ntitle{background-color:white} td.diff-otitle,td.diff-ntitle{text-align:center} -td.diff-marker{text-align:right;font-weight:bold;font-size:1.25em} +td.diff-marker{width:1.5em;text-align:center;font-weight:bold;font-size:1.25em;padding:0 0.3em} td.diff-lineno{font-weight:bold} td.diff-addedline,td.diff-deletedline,td.diff-context{font-size:88%;vertical-align:top;white-space:-moz-pre-wrap;white-space:pre-wrap} -td.diff-addedline,td.diff-deletedline{border-style:solid;border-width:1px 1px 1px 4px;border-radius:0.33em} -td.diff-addedline{border-color:#a3d3ff} -td.diff-deletedline{border-color:#ffe49c} -td.diff-context{background:#f3f3f3;color:#333333;border-style:solid;border-width:1px 1px 1px 4px;border-color:#e6e6e6;border-radius:0.33em} +td.diff-addedline,td.diff-deletedline{border-left:3px solid} +td.diff-addedline{border-color:#a3d3ff;background:#f0f8ff} +td.diff-deletedline{border-color:#ffe49c;background:#fffaf0} +td.diff-context{color:#555} .diffchange{font-weight:bold;text-decoration:none} -table.diff{border:none;width:98%;border-spacing:4px; table-layout:fixed} -td.diff-addedline .diffchange,td.diff-deletedline .diffchange{border-radius:0.33em;padding:0.25em 0} +table.diff{border:none;width:100%;border-spacing:0;border-collapse:collapse;table-layout:auto} td.diff-addedline .diffchange{background:#d8ecff} td.diff-deletedline .diffchange{background:#feeec8} -table.diff td{padding:0.33em 0.66em} -table.diff col.diff-marker{width:2%} -table.diff col.diff-content{width:48%} -table.diff td div{ word-wrap:break-word; overflow:auto} +table.diff td{padding:0.2em 0.5em} +table.diff td div{word-wrap:break-word;overflow:auto} diff --git a/static/favicon.svg b/static/favicon.svg new file mode 100644 index 0000000..181c415 --- /dev/null +++ b/static/favicon.svg @@ -0,0 +1,3 @@ + diff --git a/templates/all_done.html b/templates/all_done.html index c346482..e3135f7 100644 --- a/templates/all_done.html +++ b/templates/all_done.html @@ -1,10 +1,11 @@ {% extends "base.html" %} -{% block title %}Index{% endblock %} +{% block title %}All done{% endblock %} {% block content %} -
{{ message }}
| {{ item.title }} | -{{ item.total }} | -{{ "{:.1%}".format(item.with_links / item.total) }} | -
Find unlinked mentions of a Wikipedia article and add the links.
+| Article | +Total | +% linked | +
|---|---|---|
| {{ item.title }} | +{{ item.total }} | +{{ "{:.0%}".format(item.with_links / item.total) }} | +