logger = LoggerUtil::with_name($this::class); $this->db_logger = LoggerUtil::db_with_name($this::class); $this->tracking_list = $tracking_list; $this->wikipedia = $wikipedia; $this->mailer = $mailer; } /** * Updates all trackings that users have added. * * @param array $inputs `"password": string`: the CLI password * @return null * @throws InvalidTypeException if any of the inputs has the incorrect type * @throws InvalidValueException if the CLI password is wrong * @throws UnexpectedException if Wikipedia could not be reached * @noinspection PhpDocRedundantThrowsInspection can be thrown through {@see TrackingList::transaction()} */ public function handle(array $inputs): mixed { (new RuleSet(["password" => [new EqualsCliPasswordRule()]]))->check($inputs); // Process changes $new_deletions = []; $new_undeletions = []; $new_status_changes = []; $this->tracking_list->transaction(function () use (&$new_deletions, &$new_undeletions, &$new_status_changes) { $names = $this->tracking_list->list_all_unique_person_names(); if (empty($names)) return; try { $people_statuses = $this->wikipedia->query_people_info($names, resolve_moves: true); } catch (WikipediaException $exception) { $this->logger->error("Failed to query page info.", ["cause" => $exception, "pages" => $names]); throw new UnexpectedException("Could not reach Wikipedia. Maybe the website is down?"); } $this->tracking_list->rename_persons($people_statuses->redirects); $new_deletions = $this->tracking_list->delete_persons($people_statuses->missing); $new_undeletions = $this->tracking_list->undelete_persons(array_keys($people_statuses->results)); $new_status_changes = $this->tracking_list->update_statuses( array_combine( array_keys($people_statuses->results), array_map(fn($it) => $it["status"], $people_statuses->results) ) ); }); // Send mails, log events // TODO: Restrict number of notifications to 1 per hour (excluding "oops we're not sure" message) // TODO: Wait for some time after person has been marked as dead before sending the email $emails = []; $person_names = $new_deletions + $new_undeletions + array_keys($new_status_changes); $trackers = $this->tracking_list->list_trackers($person_names); foreach ($new_deletions as $new_deletion) { $this->db_logger->notice("Deleted article $new_deletion."); foreach ($trackers[$new_deletion] as $user_email) $emails[] = new NotifyArticleDeletedEmail($user_email, $new_deletion); } foreach ($new_undeletions as $new_undeletion) { $this->db_logger->notice("Undeleted article $new_undeletion."); foreach ($trackers[$new_undeletion] as $user_email) $emails[] = new NotifyArticleUndeletedEmail($user_email, $new_undeletion); } foreach ($new_status_changes as $person_name => $person_status) { if ($person_status === PersonStatus::Alive) $this->db_logger->notice("Person $person_name is now alive again."); foreach ($trackers[$person_name] as $user_email) $emails[] = new NotifyStatusChangedEmail($user_email, $person_name, $person_status->value); } $this->mailer->enqueue($emails); return null; } } /** * An email to inform a user a tracker person's status has changed. * * @see UpdateTrackingsAction */ class NotifyStatusChangedEmail extends Email { /** * A string identifying the type of email. */ public const TYPE = "notify-status-changed"; /** * @var string the name of the person whose status has changed */ public string $name; /** * @var string the new status of the person */ public string $new_status; /** * Constructs a new `NotifyStatusChangedEmail`. * * @param string $recipient the intended recipient of this email * @param string $name the name of the person who died * @param string $new_status the new status of the person */ public function __construct(string $recipient, string $name, string $new_status) { parent::__construct($this::TYPE . "/" . $name, $recipient); $this->name = $name; $this->new_status = $new_status; } public function get_subject(): string { return "$this->name may be $this->new_status"; } public function get_body(): string { $base_path = Config::get("server.base_path"); return "Someone has edited Wikipedia to state that $this->name is $this->new_status. " . "For more information, read their Wikipedia article at " . "https://en.wikipedia.org/wiki/" . rawurlencode($this->name) . "\n\n" . "You are receiving this message because of the preferences in your Death Notifier account. " . "To unsubscribe from these messages, go to the Death Notifier website, log in, and change your email " . "preferences." . "\n\n" . $base_path; } } /** * An email to inform a user that a tracked article has been deleted. * * @see UpdateTrackingsAction */ class NotifyArticleDeletedEmail extends Email { /** * A string identifying the type of email. */ public const TYPE = "notify-article-deleted"; /** * @var string the name of the article that was deleted */ public string $name; /** * Constructs a new `NotifyArticleDeletedEmail`. * * @param string $recipient the intended recipient of this email * @param string $name the name of the article that was deleted */ public function __construct(string $recipient, string $name) { parent::__construct($this::TYPE . "/" . $name, $recipient); $this->name = $name; } public function get_subject(): string { return "$this->name article has been deleted"; } public function get_body(): string { $base_path = Config::get("server.base_path"); return "The Wikipedia article about $this->name has been deleted. " . "Death Notifier is now unable to send you a notification if $this->name dies. " . "If the Wikipedia article is ever re-created, Death Notifier will automatically resume tracking this " . "article, and you will receive another notification." . "\n\n" . "You are receiving this message because of the preferences in your Death Notifier account. " . "To unsubscribe from these messages, go to the Death Notifier website, log in, and change your email " . "preferences." . "\n\n" . $base_path; } } /** * An email to inform a user that a tracked article has been re-created. * * @see UpdateTrackingsAction */ class NotifyArticleUndeletedEmail extends Email { /** * A string identifying the type of email. */ public const TYPE = "notify-article-undeleted"; /** * @var string the name of the article that was re-created */ public string $name; /** * Constructs a new `NotifyArticleUndeletedEmail`. * * @param string $recipient the intended recipient of this email * @param string $name the name of the article that was re-created */ public function __construct(string $recipient, string $name) { parent::__construct($this::TYPE . "/" . $name, $recipient); $this->name = $name; } public function get_subject(): string { return "$this->name article has been re-created"; } public function get_body(): string { $base_path = Config::get("server.base_path"); return "The Wikipedia article about $this->name has been re-created. " . "Death Notifier will once again track the article and notify you if $this->name dies." . "\n\n" . "You are receiving this message because of the preferences in your Death Notifier account. " . "To unsubscribe from these messages, go to the Death Notifier website, log in, and change your email " . "preferences." . "\n\n" . $base_path; } }