Detect missing people, add CLI for updating
This commit is contained in:
parent
ca2937047d
commit
e33195d629
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "fwdekker/death-notifier",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.10",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
||||
|
|
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "death-notifier",
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.10",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"author": "Florine W. Dekker",
|
||||
"browser": "dist/bundle.js",
|
||||
|
|
|
@ -128,7 +128,7 @@ function validate_has_arguments(mixed ...$arguments): ?Response
|
|||
$response = null;
|
||||
|
||||
if (isset($_POST["action"])) {
|
||||
// Process POST
|
||||
// POST requests; alter state
|
||||
switch ($_POST["action"]) {
|
||||
case "register":
|
||||
$response = validate_csrf()
|
||||
|
@ -195,17 +195,33 @@ if (isset($_POST["action"])) {
|
|||
$response = new Response("Unknown POST action '" . $_POST["action"] . "'.", false);
|
||||
break;
|
||||
}
|
||||
} else if (isset($_GET["action"])) {
|
||||
} elseif (isset($_GET["action"])) {
|
||||
// GET requests; do not alter state
|
||||
$response = match ($_GET["action"]) {
|
||||
"get-user-data" => validate_logged_in() ?? $user_manager->get_user_data($_SESSION["uuid"]),
|
||||
"list-trackings" => validate_logged_in() ?? $tracking_manager->list_trackings($_SESSION["uuid"]),
|
||||
default => new Response("Unknown GET action '" . $_GET["action"] . "'.", false),
|
||||
};
|
||||
} elseif ($argc > 1) {
|
||||
// CLI
|
||||
if (hash_equals($config["admin"]["update_secret"], "REPLACE THIS WITH A SECRET VALUE"))
|
||||
exit("Default value for 'cli_secret' detected. Feature disabled.");
|
||||
if (hash_equals($config["admin"]["update_secret"], $argv[2]))
|
||||
exit("Incorrect value for 'cli_secret'.");
|
||||
|
||||
if ($argv[1] === "update-all-trackings") {
|
||||
$logger->info("Updating all trackings.");
|
||||
$tracking_manager->update_trackings($tracking_manager->list_all_trackings());
|
||||
exit("Successfully updated all trackings.");
|
||||
} else {
|
||||
exit("Unknown CLI action '" . $argv[1] . "'.");
|
||||
}
|
||||
} else {
|
||||
$response = new Response("Unknown method.", false);
|
||||
// No action given, nothing done, so that's a success
|
||||
$response = new Response(null, true);
|
||||
}
|
||||
|
||||
//header("Content-type:application/json;charset=utf-8");
|
||||
header("Content-type:application/json;charset=utf-8");
|
||||
exit(json_encode(array(
|
||||
"message" => $response->message,
|
||||
"satisfied" => $response->satisfied,
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
;<?php exit(); ?>
|
||||
|
||||
[admin]
|
||||
# Password to use the CLI of `api.php`. Until this value is changed from its default, the feature is disabled
|
||||
cli_secret = REPLACE THIS WITH A SECRET VALUE
|
||||
|
||||
[database]
|
||||
# Relative path to SQLite database
|
||||
filename = .death-notifier.db
|
||||
|
||||
[logger]
|
||||
# File to store logs in
|
||||
file = .death-notifier.log
|
||||
# Log level. See https://seldaek.github.io/monolog/doc/01-usage.html#log-levels
|
||||
level = 300
|
||||
|
||||
[mail]
|
||||
# Whether to enable mailing
|
||||
enabled = false
|
||||
|
@ -19,9 +29,3 @@ password = TODO
|
|||
from_name = TODO
|
||||
# Email address to send test emails to
|
||||
to_address_test = TODO
|
||||
|
||||
[logger]
|
||||
# File to store logs in
|
||||
file = death-notifier.log
|
||||
# Log level. See https://seldaek.github.io/monolog/doc/01-usage.html#log-levels
|
||||
level = 300
|
||||
|
|
|
@ -26,7 +26,7 @@ function refreshTrackings() {
|
|||
const trackingPersonName = document.createElement("td");
|
||||
trackingPersonName.innerText = tracking["person_name"];
|
||||
const trackingIsDeceased = document.createElement("td");
|
||||
trackingIsDeceased.innerText = tracking["is_deceased"] === 1 ? "yes" : "no";
|
||||
trackingIsDeceased.innerText = tracking["status"];
|
||||
const trackingDelete = document.createElement("td");
|
||||
const trackingDeleteButton = document.createElement("button");
|
||||
trackingDeleteButton.innerText = "remove";
|
||||
|
|
|
@ -18,9 +18,6 @@ class Database
|
|||
*/
|
||||
public static function connect(string $filename): PDO
|
||||
{
|
||||
return new PDO("sqlite:" . $filename, options: array(
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_PERSISTENT => true
|
||||
));
|
||||
return new PDO("sqlite:" . $filename, options: array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,6 @@ class Mediawiki
|
|||
private const USER_AGENT =
|
||||
"death-notifier/%%VERSION_NUMBER%% " .
|
||||
"(https://git.fwdekker.com/tools/death-notifier; florine@fwdekker.com)";
|
||||
/**
|
||||
* Regex matching Wikipedia categories indicating deceased people.
|
||||
*/
|
||||
private const DECEASED_CATEGORY_REGEX = "/^Category:[0-9]{1,4} (BC |AD )?deaths$/";
|
||||
|
||||
/**
|
||||
* @var Logger The logger to use for logging.
|
||||
|
@ -82,6 +78,7 @@ class Mediawiki
|
|||
"action" => "query",
|
||||
"format" => "json",
|
||||
"prop" => "info",
|
||||
"redirects" => true,
|
||||
"titles" => implode("|", array_slice($titles, $i, 50))
|
||||
))["query"];
|
||||
|
||||
|
@ -94,6 +91,10 @@ class Mediawiki
|
|||
array_map($page_exists, $response_pages)
|
||||
)
|
||||
);
|
||||
|
||||
if (isset($response["normalized"]))
|
||||
foreach ($response["normalized"] as $redirect)
|
||||
$pages[$redirect["from"]] = $pages[$redirect["to"]];
|
||||
}
|
||||
|
||||
return $pages;
|
||||
|
@ -109,38 +110,41 @@ class Mediawiki
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the person is deceased, `false` if the person is alive, and `null` if the person does not have
|
||||
* a page on Wikipedia.
|
||||
* Returns a string describing `person`'s status ("deceased", "alive", "possibly alive", "missing"), or `null` if
|
||||
* the title does not refer to a page about a person on Wikipedia.
|
||||
*
|
||||
* @param mixed $person_page the page as returned by the Wikipedia API
|
||||
* @return bool|null `true` if the person is deceased, `false` if the person is alive, and `null` if the person does
|
||||
* not have a page on Wikipedia
|
||||
* @return string|null a string describing `person`'s status ("deceased", "alive", "possibly alive", "missing"), or
|
||||
* `null` if the title does not refer to a page about a person on Wikipedia
|
||||
*/
|
||||
private function person_is_deceased(mixed $person_page): ?bool
|
||||
private function person_status(mixed $person_page): ?string
|
||||
{
|
||||
if (array_key_exists("missing", $person_page) || array_key_exists("invalid", $person_page))
|
||||
return null;
|
||||
|
||||
// TODO: Detect missing people, detect presumed dead people
|
||||
$is_alive = in_array("Category:Living people", array_column($person_page["categories"], "title"));
|
||||
$is_deceased = !empty(array_filter(
|
||||
$person_page["categories"],
|
||||
fn($it) => preg_match(self::DECEASED_CATEGORY_REGEX, $it["title"])
|
||||
));
|
||||
if (!$is_alive && !$is_deceased)
|
||||
return null;
|
||||
$category_titles = array_column($person_page["categories"], "title");
|
||||
$deceased_regex = "/^Category:([0-9]{1,4}s? (BC |AD )?deaths|Year of death (missing|unknown))$/";
|
||||
|
||||
return $is_deceased;
|
||||
if (!empty(array_filter($category_titles, fn($it) => preg_match($deceased_regex, $it))))
|
||||
return "deceased";
|
||||
elseif (in_array("Category:Possibly living people", $category_titles))
|
||||
return "possibly alive";
|
||||
elseif (in_array("Category:Missing people", $category_titles))
|
||||
return "missing";
|
||||
elseif (in_array("Category:Living people", $category_titles))
|
||||
return "alive";
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for each person whether they are alive according to Wikipedia's categorization.
|
||||
* Checks for each person what their status (dead, alive, missing) is according to Wikipedia's categorization.
|
||||
*
|
||||
* @param array<string> $people_names the names of the people to check aliveness of
|
||||
* @return array<string, ?bool> maps each requested person's name to a boolean indicating whether they are deceased.
|
||||
* If a page does not exist, it is mapped to a `null`
|
||||
* @return array<string, ?string> maps each requested person's name to a string describing their status ("deceased",
|
||||
* "alive", "possibly alive", "missing"). If a page does not exist, it is mapped to `null`
|
||||
*/
|
||||
public function people_are_deceased(array $people_names): array
|
||||
public function people_statuses(array $people_names): array
|
||||
{
|
||||
try {
|
||||
$pages = [];
|
||||
|
@ -161,7 +165,7 @@ class Mediawiki
|
|||
$pages,
|
||||
array_combine(
|
||||
array_column($response_pages, "title"),
|
||||
array_map(fn($it) => $this->person_is_deceased($it), $response_pages)
|
||||
array_map(fn($it) => $this->person_status($it), $response_pages)
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ class TrackingManager
|
|||
public function install(): void
|
||||
{
|
||||
$conn = Database::connect($this->db_filename);
|
||||
$conn->exec("CREATE TABLE trackings(user_uuid text not null, person_name text not null, is_deceased int not null default 0, PRIMARY KEY (user_uuid, person_name));");
|
||||
$conn->exec("CREATE TABLE trackings(user_uuid text not null, person_name text not null, status text not null, PRIMARY KEY (user_uuid, person_name));");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -69,8 +69,8 @@ class TrackingManager
|
|||
if (!$this->mediawiki->pages_exist([$person_name])[$person_name])
|
||||
return new Response("Page does not exist.", false);
|
||||
|
||||
$is_deceased = $this->mediawiki->people_are_deceased([$person_name])[$person_name];
|
||||
if ($is_deceased === null)
|
||||
$status = $this->mediawiki->people_statuses([$person_name])[$person_name];
|
||||
if ($status === null)
|
||||
return new Response("Page does not refer to a person.", false);
|
||||
|
||||
$conn = Database::connect($this->db_filename);
|
||||
|
@ -85,10 +85,10 @@ class TrackingManager
|
|||
return new Response("Tracking already exists.", false);
|
||||
}
|
||||
|
||||
$stmt = $conn->prepare("INSERT INTO trackings (user_uuid, person_name, is_deceased) VALUES (:user_uuid, :person_name, :is_deceased);");
|
||||
$stmt = $conn->prepare("INSERT INTO trackings (user_uuid, person_name, status) VALUES (:user_uuid, :person_name, :status);");
|
||||
$stmt->bindValue(":user_uuid", $user_uuid);
|
||||
$stmt->bindValue(":person_name", $person_name);
|
||||
$stmt->bindValue(":is_deceased", $is_deceased);
|
||||
$stmt->bindValue(":status", $status);
|
||||
$stmt->execute();
|
||||
|
||||
$conn->commit();
|
||||
|
@ -125,7 +125,7 @@ class TrackingManager
|
|||
public function list_trackings(string $user_uuid): Response
|
||||
{
|
||||
$conn = Database::connect($this->db_filename);
|
||||
$stmt = $conn->prepare("SELECT person_name, is_deceased FROM trackings WHERE user_uuid=:user_uuid;");
|
||||
$stmt = $conn->prepare("SELECT person_name, status FROM trackings WHERE user_uuid=:user_uuid;");
|
||||
$stmt->bindValue(":user_uuid", $user_uuid);
|
||||
$stmt->execute();
|
||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
@ -155,13 +155,13 @@ class TrackingManager
|
|||
public function update_trackings(array $people_names): void
|
||||
{
|
||||
// TODO: Handle removed pages
|
||||
$people_statuses = $this->mediawiki->people_are_deceased($people_names);
|
||||
$people_statuses = $this->mediawiki->people_statuses($people_names);
|
||||
|
||||
$conn = Database::connect($this->db_filename);
|
||||
$stmt = $conn->prepare("UPDATE trackings SET is_deceased=:is_deceased WHERE person_name=:person_name;");
|
||||
$stmt->bindParam(":is_deceased", $person_is_deceased);
|
||||
$stmt = $conn->prepare("UPDATE trackings SET status=:status WHERE person_name=:person_name;");
|
||||
$stmt->bindParam(":status", $person_status);
|
||||
$stmt->bindParam(":person_name", $person_name);
|
||||
foreach ($people_statuses as $person_name => $person_is_deceased)
|
||||
foreach ($people_statuses as $person_name => $person_status)
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue