309 lines
10 KiB
PHP
309 lines
10 KiB
PHP
<?php
|
|
|
|
namespace com\fwdekker\deathnotifier\tracking;
|
|
|
|
use com\fwdekker\deathnotifier\Action;
|
|
use com\fwdekker\deathnotifier\Config;
|
|
use com\fwdekker\deathnotifier\LoggerUtil;
|
|
use com\fwdekker\deathnotifier\mailer\Email;
|
|
use com\fwdekker\deathnotifier\mailer\EmailQueue;
|
|
use com\fwdekker\deathnotifier\UnexpectedException;
|
|
use com\fwdekker\deathnotifier\validation\EqualsCliPasswordRule;
|
|
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
|
|
use com\fwdekker\deathnotifier\validation\InvalidValueException;
|
|
use com\fwdekker\deathnotifier\validation\RuleSet;
|
|
use com\fwdekker\deathnotifier\wikipedia\PersonStatus;
|
|
use com\fwdekker\deathnotifier\wikipedia\Wikipedia;
|
|
use com\fwdekker\deathnotifier\wikipedia\WikipediaException;
|
|
use Monolog\Logger;
|
|
|
|
|
|
/**
|
|
* Updates all trackings that users have added.
|
|
*
|
|
* @see NotifyStatusChangedEmail
|
|
* @see NotifyArticleDeletedEmail
|
|
* @see NotifyArticleUndeletedEmail
|
|
*/
|
|
class UpdateTrackingsAction extends Action
|
|
{
|
|
/**
|
|
* @var Logger the logger to log with
|
|
*/
|
|
private readonly Logger $logger;
|
|
/**
|
|
* @var TrackingList the list of trackings to update
|
|
*/
|
|
private readonly TrackingList $tracking_list;
|
|
/**
|
|
* @var Wikipedia the API of Wikipedia
|
|
*/
|
|
private readonly Wikipedia $wikipedia;
|
|
/**
|
|
* @var EmailQueue the queue to add notifications to
|
|
*/
|
|
private readonly EmailQueue $mailer;
|
|
|
|
|
|
/**
|
|
* Constructs a new `UpdateTrackingsAction`.
|
|
*
|
|
* @param TrackingList $tracking_list the list of trackings to update
|
|
* @param Wikipedia $wikipedia the API of Wikipedia
|
|
* @param EmailQueue $mailer the queue to add notifications to
|
|
*/
|
|
public function __construct(TrackingList $tracking_list, Wikipedia $wikipedia, EmailQueue $mailer)
|
|
{
|
|
$this->logger = LoggerUtil::with_name($this::class);
|
|
$this->tracking_list = $tracking_list;
|
|
$this->wikipedia = $wikipedia;
|
|
$this->mailer = $mailer;
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates all trackings that users have added.
|
|
*
|
|
* @param array<int|string, mixed> $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
|
|
$logger = LoggerUtil::with_name($this::class);
|
|
$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) {
|
|
$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) {
|
|
$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)
|
|
$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;
|
|
}
|
|
}
|