Skip invalid and protected candidates
This commit is contained in:
parent
bd0c1a26c2
commit
93f49c5554
3 changed files with 223 additions and 17 deletions
74
web_view.py
74
web_view.py
|
|
@ -1,7 +1,6 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
import html
|
||||
import itertools
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
|
@ -37,6 +36,14 @@ class Hit(typing.TypedDict):
|
|||
timestamp: str
|
||||
|
||||
|
||||
class PageInfo(typing.TypedDict, total=False):
|
||||
"""Wikipedia page metadata."""
|
||||
|
||||
pageid: int
|
||||
title: str
|
||||
protection: list[dict[str, str]]
|
||||
|
||||
|
||||
def render_error(message: str) -> str:
|
||||
"""Render shared error page."""
|
||||
return flask.render_template("error.html", message=message)
|
||||
|
|
@ -115,13 +122,60 @@ def search_count_with_link(q: str, redirect_to: str | None = None) -> int:
|
|||
return count
|
||||
|
||||
|
||||
def chunks(items: list[int], size: int) -> typing.Iterator[list[int]]:
|
||||
"""Yield fixed-size chunks from a list."""
|
||||
for offset in range(0, len(items), size):
|
||||
yield items[offset : offset + size]
|
||||
|
||||
|
||||
def get_page_info_by_id(pageids: list[int]) -> dict[int, PageInfo]:
|
||||
"""Fetch metadata for pages by page ID."""
|
||||
if not pageids:
|
||||
return {}
|
||||
|
||||
pages: dict[int, PageInfo] = {}
|
||||
for pageid_chunk in chunks(pageids, 50):
|
||||
ret = api.api_get(
|
||||
{
|
||||
"prop": "info",
|
||||
"inprop": "protection",
|
||||
"pageids": "|".join(str(pageid) for pageid in pageid_chunk),
|
||||
}
|
||||
)
|
||||
for page in ret["query"]["pages"]:
|
||||
pageid = typing.cast(int, page["pageid"])
|
||||
pages[pageid] = typing.cast(PageInfo, page)
|
||||
return pages
|
||||
|
||||
|
||||
def is_edit_protected(page: PageInfo | None) -> bool:
|
||||
"""Check if a page has edit protection."""
|
||||
if not page:
|
||||
return False
|
||||
return any(
|
||||
protection.get("type") == "edit" or protection.get("action") == "edit"
|
||||
for protection in page.get("protection", [])
|
||||
)
|
||||
|
||||
|
||||
def filter_edit_protected_hits(hits: list[Hit]) -> list[Hit]:
|
||||
"""Remove candidates that cannot be edited because of page protection."""
|
||||
page_info = get_page_info_by_id([hit["pageid"] for hit in hits])
|
||||
return [
|
||||
hit for hit in hits if not is_edit_protected(page_info.get(hit["pageid"]))
|
||||
]
|
||||
|
||||
|
||||
def search_no_link(q: str, redirect_to: str | None = None) -> tuple[int, list[Hit]]:
|
||||
"""Search for mentions of article title with no link included."""
|
||||
exclude = f' -linksto:"{q}"'
|
||||
if redirect_to:
|
||||
exclude += f' -linksto:"{redirect_to}"'
|
||||
query = run_search(article_title_to_search_query(q) + exclude, "max")
|
||||
return (query["searchinfo"]["totalhits"], query["search"])
|
||||
return (
|
||||
query["searchinfo"]["totalhits"],
|
||||
filter_edit_protected_hits(query["search"]),
|
||||
)
|
||||
|
||||
|
||||
@app.before_request
|
||||
|
|
@ -317,6 +371,9 @@ def match_type(q: str, snippet: str) -> str | None:
|
|||
|
||||
def _record_skip(from_title: str, hit_title: str) -> None:
|
||||
"""Record that a candidate was skipped or saved for this article."""
|
||||
hit_title = hit_title.strip()
|
||||
if not hit_title:
|
||||
return
|
||||
skipped: dict[str, list[str]] = flask.session.get("skipped", {})
|
||||
article_skipped = skipped.get(from_title, [])
|
||||
if hit_title not in article_skipped:
|
||||
|
|
@ -371,10 +428,8 @@ def article_page(url_title: str) -> str | Response:
|
|||
render_mediawiki_error(e), 502
|
||||
)
|
||||
|
||||
# Filter out candidates already processed this session
|
||||
session_skipped: set[str] = set(
|
||||
flask.session.get("skipped", {}).get(from_title, [])
|
||||
)
|
||||
article_skipped = flask.session.get("skipped", {}).get(from_title, [])
|
||||
session_skipped: set[str] = set(article_skipped)
|
||||
|
||||
# If a specific candidate was requested, move it to the front
|
||||
title_param = flask.request.args.get("title")
|
||||
|
|
@ -382,12 +437,6 @@ def article_page(url_title: str) -> str | Response:
|
|||
hits = [h for h in hits if h["title"] == title_param] + \
|
||||
[h for h in hits if h["title"] != title_param]
|
||||
|
||||
# Record and apply explicit skip-past
|
||||
after = flask.request.args.get("after")
|
||||
if after:
|
||||
_record_skip(from_title, after)
|
||||
session_skipped.add(after)
|
||||
|
||||
hits = [h for h in hits if h["title"] not in session_skipped
|
||||
and h["title"] != from_title and h["title"] != case_flip_first(from_title)]
|
||||
|
||||
|
|
@ -404,6 +453,7 @@ def article_page(url_title: str) -> str | Response:
|
|||
with_link=with_link,
|
||||
hits=hits,
|
||||
url_title=url_title,
|
||||
skipped_titles=article_skipped,
|
||||
saves_this_session=saves_this_session,
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue