Detect missing people, add CLI for updating
This commit is contained in:
parent
ca2937047d
commit
e33195d629
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "fwdekker/death-notifier",
|
"name": "fwdekker/death-notifier",
|
||||||
"description": "Get notified when a famous person dies.",
|
"description": "Get notified when a famous person dies.",
|
||||||
"version": "0.0.9",
|
"version": "0.0.10",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
||||||
|
|
Binary file not shown.
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "death-notifier",
|
"name": "death-notifier",
|
||||||
"version": "0.0.9",
|
"version": "0.0.10",
|
||||||
"description": "Get notified when a famous person dies.",
|
"description": "Get notified when a famous person dies.",
|
||||||
"author": "Florine W. Dekker",
|
"author": "Florine W. Dekker",
|
||||||
"browser": "dist/bundle.js",
|
"browser": "dist/bundle.js",
|
||||||
|
|
|
@ -128,7 +128,7 @@ function validate_has_arguments(mixed ...$arguments): ?Response
|
||||||
$response = null;
|
$response = null;
|
||||||
|
|
||||||
if (isset($_POST["action"])) {
|
if (isset($_POST["action"])) {
|
||||||
// Process POST
|
// POST requests; alter state
|
||||||
switch ($_POST["action"]) {
|
switch ($_POST["action"]) {
|
||||||
case "register":
|
case "register":
|
||||||
$response = validate_csrf()
|
$response = validate_csrf()
|
||||||
|
@ -195,17 +195,33 @@ if (isset($_POST["action"])) {
|
||||||
$response = new Response("Unknown POST action '" . $_POST["action"] . "'.", false);
|
$response = new Response("Unknown POST action '" . $_POST["action"] . "'.", false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (isset($_GET["action"])) {
|
} elseif (isset($_GET["action"])) {
|
||||||
|
// GET requests; do not alter state
|
||||||
$response = match ($_GET["action"]) {
|
$response = match ($_GET["action"]) {
|
||||||
"get-user-data" => validate_logged_in() ?? $user_manager->get_user_data($_SESSION["uuid"]),
|
"get-user-data" => validate_logged_in() ?? $user_manager->get_user_data($_SESSION["uuid"]),
|
||||||
"list-trackings" => validate_logged_in() ?? $tracking_manager->list_trackings($_SESSION["uuid"]),
|
"list-trackings" => validate_logged_in() ?? $tracking_manager->list_trackings($_SESSION["uuid"]),
|
||||||
default => new Response("Unknown GET action '" . $_GET["action"] . "'.", false),
|
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 {
|
} 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(
|
exit(json_encode(array(
|
||||||
"message" => $response->message,
|
"message" => $response->message,
|
||||||
"satisfied" => $response->satisfied,
|
"satisfied" => $response->satisfied,
|
||||||
|
|
|
@ -1,9 +1,19 @@
|
||||||
;<?php exit(); ?>
|
;<?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]
|
[database]
|
||||||
# Relative path to SQLite database
|
# Relative path to SQLite database
|
||||||
filename = .death-notifier.db
|
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]
|
[mail]
|
||||||
# Whether to enable mailing
|
# Whether to enable mailing
|
||||||
enabled = false
|
enabled = false
|
||||||
|
@ -19,9 +29,3 @@ password = TODO
|
||||||
from_name = TODO
|
from_name = TODO
|
||||||
# Email address to send test emails to
|
# Email address to send test emails to
|
||||||
to_address_test = TODO
|
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");
|
const trackingPersonName = document.createElement("td");
|
||||||
trackingPersonName.innerText = tracking["person_name"];
|
trackingPersonName.innerText = tracking["person_name"];
|
||||||
const trackingIsDeceased = document.createElement("td");
|
const trackingIsDeceased = document.createElement("td");
|
||||||
trackingIsDeceased.innerText = tracking["is_deceased"] === 1 ? "yes" : "no";
|
trackingIsDeceased.innerText = tracking["status"];
|
||||||
const trackingDelete = document.createElement("td");
|
const trackingDelete = document.createElement("td");
|
||||||
const trackingDeleteButton = document.createElement("button");
|
const trackingDeleteButton = document.createElement("button");
|
||||||
trackingDeleteButton.innerText = "remove";
|
trackingDeleteButton.innerText = "remove";
|
||||||
|
|
|
@ -18,9 +18,6 @@ class Database
|
||||||
*/
|
*/
|
||||||
public static function connect(string $filename): PDO
|
public static function connect(string $filename): PDO
|
||||||
{
|
{
|
||||||
return new PDO("sqlite:" . $filename, options: array(
|
return new PDO("sqlite:" . $filename, options: array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
||||||
PDO::ATTR_PERSISTENT => true
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,6 @@ class Mediawiki
|
||||||
private const USER_AGENT =
|
private const USER_AGENT =
|
||||||
"death-notifier/%%VERSION_NUMBER%% " .
|
"death-notifier/%%VERSION_NUMBER%% " .
|
||||||
"(https://git.fwdekker.com/tools/death-notifier; florine@fwdekker.com)";
|
"(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.
|
* @var Logger The logger to use for logging.
|
||||||
|
@ -82,6 +78,7 @@ class Mediawiki
|
||||||
"action" => "query",
|
"action" => "query",
|
||||||
"format" => "json",
|
"format" => "json",
|
||||||
"prop" => "info",
|
"prop" => "info",
|
||||||
|
"redirects" => true,
|
||||||
"titles" => implode("|", array_slice($titles, $i, 50))
|
"titles" => implode("|", array_slice($titles, $i, 50))
|
||||||
))["query"];
|
))["query"];
|
||||||
|
|
||||||
|
@ -94,6 +91,10 @@ class Mediawiki
|
||||||
array_map($page_exists, $response_pages)
|
array_map($page_exists, $response_pages)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isset($response["normalized"]))
|
||||||
|
foreach ($response["normalized"] as $redirect)
|
||||||
|
$pages[$redirect["from"]] = $pages[$redirect["to"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $pages;
|
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
|
* Returns a string describing `person`'s status ("deceased", "alive", "possibly alive", "missing"), or `null` if
|
||||||
* a page on Wikipedia.
|
* 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
|
* @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
|
* @return string|null a string describing `person`'s status ("deceased", "alive", "possibly alive", "missing"), or
|
||||||
* not have a page on Wikipedia
|
* `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))
|
if (array_key_exists("missing", $person_page) || array_key_exists("invalid", $person_page))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// TODO: Detect missing people, detect presumed dead people
|
$category_titles = array_column($person_page["categories"], "title");
|
||||||
$is_alive = in_array("Category:Living people", array_column($person_page["categories"], "title"));
|
$deceased_regex = "/^Category:([0-9]{1,4}s? (BC |AD )?deaths|Year of death (missing|unknown))$/";
|
||||||
$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;
|
|
||||||
|
|
||||||
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
|
* @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.
|
* @return array<string, ?string> maps each requested person's name to a string describing their status ("deceased",
|
||||||
* If a page does not exist, it is mapped to a `null`
|
* "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 {
|
try {
|
||||||
$pages = [];
|
$pages = [];
|
||||||
|
@ -161,7 +165,7 @@ class Mediawiki
|
||||||
$pages,
|
$pages,
|
||||||
array_combine(
|
array_combine(
|
||||||
array_column($response_pages, "title"),
|
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
|
public function install(): void
|
||||||
{
|
{
|
||||||
$conn = Database::connect($this->db_filename);
|
$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])
|
if (!$this->mediawiki->pages_exist([$person_name])[$person_name])
|
||||||
return new Response("Page does not exist.", false);
|
return new Response("Page does not exist.", false);
|
||||||
|
|
||||||
$is_deceased = $this->mediawiki->people_are_deceased([$person_name])[$person_name];
|
$status = $this->mediawiki->people_statuses([$person_name])[$person_name];
|
||||||
if ($is_deceased === null)
|
if ($status === null)
|
||||||
return new Response("Page does not refer to a person.", false);
|
return new Response("Page does not refer to a person.", false);
|
||||||
|
|
||||||
$conn = Database::connect($this->db_filename);
|
$conn = Database::connect($this->db_filename);
|
||||||
|
@ -85,10 +85,10 @@ class TrackingManager
|
||||||
return new Response("Tracking already exists.", false);
|
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(":user_uuid", $user_uuid);
|
||||||
$stmt->bindValue(":person_name", $person_name);
|
$stmt->bindValue(":person_name", $person_name);
|
||||||
$stmt->bindValue(":is_deceased", $is_deceased);
|
$stmt->bindValue(":status", $status);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
$conn->commit();
|
$conn->commit();
|
||||||
|
@ -125,7 +125,7 @@ class TrackingManager
|
||||||
public function list_trackings(string $user_uuid): Response
|
public function list_trackings(string $user_uuid): Response
|
||||||
{
|
{
|
||||||
$conn = Database::connect($this->db_filename);
|
$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->bindValue(":user_uuid", $user_uuid);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
@ -155,13 +155,13 @@ class TrackingManager
|
||||||
public function update_trackings(array $people_names): void
|
public function update_trackings(array $people_names): void
|
||||||
{
|
{
|
||||||
// TODO: Handle removed pages
|
// 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);
|
$conn = Database::connect($this->db_filename);
|
||||||
$stmt = $conn->prepare("UPDATE trackings SET is_deceased=:is_deceased WHERE person_name=:person_name;");
|
$stmt = $conn->prepare("UPDATE trackings SET status=:status WHERE person_name=:person_name;");
|
||||||
$stmt->bindParam(":is_deceased", $person_is_deceased);
|
$stmt->bindParam(":status", $person_status);
|
||||||
$stmt->bindParam(":person_name", $person_name);
|
$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();
|
$stmt->execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue