diff --git a/includes/class-Bot.php b/includes/class-Bot.php index 341ee87..a6f59b3 100644 --- a/includes/class-Bot.php +++ b/includes/class-Bot.php @@ -1,289 +1,293 @@ . namespace itwikidelbot; use cli\Log; use DateTime; use DateInterval; use Exception; /** * The Bot class helps in running the bot */ class Bot { /** * The date * * @var DateTime */ private $dateTime; /** * A cache of already done dates. * It's an array of booleans like [year][month] = true. * * @var array */ private $cache = []; /** * Construct * * @param $date DateTime */ public function __construct( DateTime $date = null ) { if( ! $date ) { $date = new DateTime(); } $this->setDate( $date ); } /** * Static construct * * @param $date string */ public static function createFromString( $date = 'now' ) { return new self( new DateTime( $date ) ); } /** * Static construct * * @param $y int Year * @param $m int Month 1-12 * @param $d int Day 1-31 */ public static function createFromYearMonthDay( $y, $m, $d ) { return new self( DateTime::createFromFormat( "Y m d", "$y $m $d") ); } /** * Get the date * * @return DateTime */ public function getDate() { return $this->dateTime; } /** * Set the date * * @param $date DateTime * @return self */ public function setDate( DateTime $date ) { $this->dateTime = $date; } /** * Add a day * * @return self */ public function nextDay() { return $this->addDays( 1 ); } /** * Add a day * * @return self */ public function previousDay() { return $this->subDays( 1 ); } /** * Add a certain number of days * * @param $days int * @return self */ public function addDays( $days ) { $this->getDate()->add( new DateInterval( sprintf( 'P%dD', $days ) ) ); return $this; } /** * Subtract a certain number of days * * @param $days int * @return self */ public function subDays( $days ) { $this->getDate()->sub( new DateInterval( sprintf( 'P%dD', $days ) ) ); return $this; } /** * Fetch the last bot edit on the next page * * @return DateTime|null */ public function fetchLastedit() { $lastedit = null; try { $lastedit = PageYearMonthDayPDCsCount::createFromDateTime( $this->getDate() ) ->fetchLasteditDate(); - } catch( Exception $e ) { + } catch( PDCMissingException $e ) { // Unexisting. OK. + Log::debug( $e->getMessage() ); + } catch( PDCWithoutCreationDateException $e ) { + // Happened once. Just try to create. + Log::debug( $e->getMessage() ); } return $lastedit; } /** * Is the last edit date older than some seconds? (on the next page) * * @param $seconds int * @return bool */ public function isLasteditOlderThanSeconds( $seconds ) { $lastedit = $this->fetchLastedit(); if( ! $lastedit ) { return true; // Unexisting. OK. } return time() - $lastedit->format( 'U' ) > $seconds; } /** * Is the last edit date older than some minutes? (on the next page) * * @param $minutes int * @return bool */ public function isLasteditOlderThanMinutes( $minutes ) { return $this->isLasteditOlderThanSeconds( 60 * $minutes ); } /** * Run the bot at the internal date * * @TODO: do not repeat twice the same yearly and montly categories * @return self */ public function run() { $cache = & $this->cache; // date initialization $date = $this->getDate(); $year = $date->format( 'Y' ); $month = $date->format( 'n' ); // 1-12 $day = $date->format( 'j' ); // yearly category $y_category = new CategoryYear( $year ); // monthly category $m_category = new CategoryYearMonth( $year, $month ); // create all the PDC types $category_types = []; foreach( CategoryYearMonthDayTypes::all() as $CategoryType ) { $category_types[] = new $CategoryType( $year, $month, $day ); } // all the categories $all_categories = $category_types; if( ! isset( $cache[ $month ] ) ) { $all_categories[] = $m_category; } if( ! isset( $cache[ $year ] ) ) { $all_categories[] = $y_category; } // check in bulk if the categories already exist Pages::populateWheneverTheyExist( $all_categories ); // create the yearly category (once) if( ! isset( $cache[ $year ] ) ) { $cache[ $year ] = []; $y_category->saveIfNotExists(); } // create the monthly category (once) $cache = & $cache[ $year ]; if( ! isset( $cache[ $month ] ) ) { $cache[ $month ] = true; $m_category->saveIfNotExists(); } Log::info( "work on $year/$month/$day" ); // PDCs indexed by page ID $pdcs = []; // handle every PDC type foreach( $category_types as $category_type ) { // fetch PDCs from this type $category_type_pdcs = $category_type->fetchPDCs(); // save the specific daily category type only if it's not empty // ...or only if it's the main category (that can be without pages) if( $category_type_pdcs || get_class( $category_type ) === CategoryYearMonthDay::class ) { $category_type->saveIfNotExists(); } // merge the same PDCs into one foreach( $category_type_pdcs as $category_type_pdc ) { $id = $category_type_pdc->getID(); if( isset( $pdcs[ $id ] ) ) { $pdcs[ $id ]->merge( $category_type_pdc ); } else { $pdcs[ $id ] = $category_type_pdc; } } } // select only the PDCs that belong to this date $pdcs = PDCs::filterByDate( $pdcs, $date ); Log::info( sprintf( sprintf( "found %d PDCs", count( $pdcs ) ) ) ); // populate missing informations PDCs::populateMissingInformations( $pdcs ); // sort by creation date PDCs::sortByCreationDate( $pdcs ); // index then by their PDC_TYPE $pdcs_by_type = PDCs::indexByType( $pdcs ); // save the counting page PageYearMonthDayPDCsCount::createFromDateTimePDCs( $this->getDate(), $pdcs_by_type ) ->save(); // save the log page PageYearMonthDayPDCsLog::createFromDateTimePDCs( $this->getDate(), $pdcs_by_type ) ->save(); return $this; } } diff --git a/includes/class-PDCMissingException.php b/includes/class-PDCMissingException.php new file mode 100644 index 0000000..1b6e75b --- /dev/null +++ b/includes/class-PDCMissingException.php @@ -0,0 +1,22 @@ +. + +namespace itwikidelbot; + +/** + * Exception throw when a "Procedura di Cancellazione" (PDC) is missing. + */ +class PDCMissingException extends PDCException {} diff --git a/includes/class-PDCWithoutCreationDateException.php b/includes/class-PDCWithoutCreationDateException.php new file mode 100644 index 0000000..33769b1 --- /dev/null +++ b/includes/class-PDCWithoutCreationDateException.php @@ -0,0 +1,19 @@ +. + +namespace itwikidelbot; + +class PDCWithoutCreationDateException extends Exception {} diff --git a/includes/class-Page.php b/includes/class-Page.php index 9118931..b8d246b 100644 --- a/includes/class-Page.php +++ b/includes/class-Page.php @@ -1,216 +1,226 @@ . namespace itwikidelbot; use DateTime; use DateTimeZone; use cli\Input; use cli\Log; use wm\WikipediaIt; use mw\Tokens; /** * Handle a page */ class Page { /** * Time zone of Italian Wikipedia community. * * @var string */ const COMMUNITY_TIMEZONE = 'Europe/Rome'; /** * Enable this flag to ask for every changes * * @var bool */ public static $ASK_BEFORE_SAVING = false; /** * @var string Page title with prefix */ private $title; /** * Cache for the existing status of this page * * @var bool */ private $exists; /** * Construct a Page * * @param $title Page title with its prefix */ public function __construct( $title ) { $this->title = $title; } /** * Get the page title with its prefix * * @return string */ public function getTitle() { return $this->title; } /** * Save this page * * @param $content string Page content * @param $summary string Edit summary * @return mixed */ public function saveByContentSummary( $content, $summary ) { $title = $this->getTitle(); $api = self::api(); $args = [ 'action' => 'edit', 'title' => $title, 'text' => $content, 'summary' => $summary, 'token' => $api->login()->getToken( Tokens::CSRF ), 'bot' => 1, ]; if( self::$ASK_BEFORE_SAVING ) { print_r( $args ); if( 'y' !== Input::yesNoQuestion( "Save?" ) ) { return false; } } Log::info( "writing [[$title]]" ); return $api->post( $args ); } /** * Save this page if it does not exist * * @param $content string Page content * @param $summary string Edit summary * @return bool|mixed False if not created */ public function saveByContentSummaryIfNotExists( $content, $summary ) { if( ! $this->exists() ) { return $this->saveByContentSummary( $content, $summary ); } return false; } /** * Set internally if this page exists * * @param $exists bool * @return bool|mixed False if not created */ public function setIfExists( $exists ) { $this->exists = $exists; } /** * Check if this page exists (the result is cached) * * @return bool */ public function exists() { if( null === $this->exists ) { $result = self::api()->fetch( [ 'action' => 'query', 'prop' => 'info', 'titles' => $this->getTitle(), ] ); foreach( $result->query->pages as $pageid => $page ) { $this->exists = ! isset( $page->missing ); } } return $this->exists; } /** * Fetch the first revision date by direction * * @param $dir string direction * @return DateTime */ public function fetchFirstRevisionDateByDirection( $direction ) { $response = self::api()->fetch( [ 'action' => 'query', 'titles' => $this->getTitle(), 'prop' => 'revisions', 'rvprop' => [ 'timestamp' ], 'rvlimit' => 1, 'rvdir' => $direction, ] ); foreach( $response->query->pages as $page ) { if( isset( $page->revisions ) ) { foreach( $page->revisions as $revision ) { return self::createDateTimeFromString( $revision->timestamp ); } } + if( isset( $page->missing ) ) { + throw new PDCMissingException( sprintf( + "The page [[%s]] is missing", + $this->getTitle() + ) ); + } } - throw new \Exception( 'unable to fetch the creation date' ); + throw new PDCWithoutCreationDateException( sprintf( + "The page [[%s]] has not a revision with a creation date (using direction %s)", + $this->title(), + $direction + ) ); } /** * Fetch the creation date of this page * * @return DateTime */ public function fetchCreationDate() { return $this->fetchFirstRevisionDateByDirection( 'newer' ); } /** * Fetch the creation date of this page * * @return DateTime */ public function fetchLasteditDate() { return $this->fetchFirstRevisionDateByDirection( 'older' ); } /** * Get the API related to this page * * @return mw\API */ public static function api() { return WikipediaIt::instance(); } /** * Create a DateTime object from a MediaWiki formatted date * * MediaWiki dates are formatted following the ISO8601 standard * and you may want to specify your community timezone. * * @param $datetime string * @return DateTime */ public static function createDateTimeFromString( $datetime ) { return DateTime::createFromFormat( DateTime::ISO8601, $datetime ) ->setTimezone( new DateTimeZone( self::COMMUNITY_TIMEZONE ) ); } }