death-notifier/src/main/php/com/fwdekker/deathnotifier/tracking/UpdateTrackingsAction.php

291 lines
9.3 KiB
PHP

<?php
namespace com\fwdekker\deathnotifier\tracking;
use com\fwdekker\deathnotifier\Action;
use com\fwdekker\deathnotifier\ActionException;
use com\fwdekker\deathnotifier\ActionMethod;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\Database;
use com\fwdekker\deathnotifier\LoggerUtil;
use com\fwdekker\deathnotifier\mailer\Email;
use com\fwdekker\deathnotifier\mailer\MailManager;
use com\fwdekker\deathnotifier\mediawiki\MediaWiki;
use com\fwdekker\deathnotifier\mediawiki\MediaWikiException;
use com\fwdekker\deathnotifier\validator\IsEqualToRule;
use Monolog\Logger;
use PDO;
/**
* Updates all trackings that users have added.
*/
class UpdateTrackingsAction extends Action
{
/**
* @var Logger the logger to log with
*/
private Logger $logger;
/**
* @var PDO the database connection to interact with
*/
private readonly PDO $conn;
/**
* @var TrackingManager the manager through which trackings should be updated
*/
private readonly TrackingManager $tracking_manager;
/**
* @var MediaWiki the instance to connect to Wikipedia with
*/
private readonly MediaWiki $mediawiki;
/**
* @var MailManager the mailer to send emails with
*/
private readonly MailManager $mailer;
/**
* Constructs a new `UpdateTrackingsAction`.
*
* @param PDO $conn the database connection to interact with
* @param TrackingManager $tracking_manager the manager through which trackings should be updated
* @param MediaWiki $mediawiki the instance to connect to Wikipedia with
* @param MailManager $mailer the mailer to send emails with
*/
public function __construct(PDO $conn, TrackingManager $tracking_manager, MediaWiki $mediawiki, MailManager $mailer)
{
parent::__construct(
ActionMethod::CLI,
"update-trackings",
rule_lists: [
"password" => [new IsEqualToRule(Config::get()["admin"]["cli_secret"], "Incorrect password.")]
],
);
$this->logger = LoggerUtil::with_name($this::class);
$this->conn = $conn;
$this->tracking_manager = $tracking_manager;
$this->mediawiki = $mediawiki;
$this->mailer = $mailer;
}
/**
* Updates all trackings that users have added.
*
* @return null
* @throws ActionException if the Wikipedia API could not be reached
*/
public function handle(): mixed
{
$names = $this->tracking_manager->list_all_unique_person_names();
if (empty($names)) return null;
// Fetch changes
try {
$people_statuses = $this->mediawiki->query_person_info($names);
} catch (MediaWikiException $exception) {
$this->logger->error("Failed to query page info.", ["cause" => $exception, "pages" => $names]);
throw new ActionException("Could not reach Wikipedia. Maybe the website is down?");
}
// Process changes
$actual_deletions = [];
$undeletions = [];
$status_changes = [];
Database::transaction(
$this->conn,
function () use (
$people_statuses,
&$actual_deletions,
&$undeletions,
&$status_changes
) {
$this->tracking_manager->rename_persons($people_statuses->redirects);
$actual_deletions = $this->tracking_manager->delete_persons($people_statuses->missing);
[$undeletions, $status_changes] = $this->tracking_manager->update_statuses($people_statuses->results);
}
);
// Send mails
// TODO: Restrict number of notifications to 1 per hour (excluding "oops we're not sure" message)
// TODO: Reuse `stmt`s for listing trackers and queueing emails to reduce overheads
foreach ($actual_deletions as $deletion)
foreach ($this->tracking_manager->list_trackers($deletion) as $user_email)
$this->mailer->queue_email(new NotifyArticleDeletedEmail($user_email, $deletion));
foreach ($undeletions as $undeletion)
foreach ($this->tracking_manager->list_trackers($undeletion) as $user_email)
$this->mailer->queue_email(new NotifyArticleUndeletedEmail($user_email, $undeletion));
foreach ($status_changes as $person_name => $person_status)
foreach ($this->tracking_manager->list_trackers($person_name) as $user_email)
$this->mailer->queue_email(new NotifyStatusChangedEmail($user_email, $person_name, $person_status));
return null;
}
}
/**
* An email to inform a user that a tracked article has been deleted.
*/
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 the 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.
*/
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 the 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;
}
}
/**
* An email to inform a user a tracker person's status has changed.
*/
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 the 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;
}
}