User:B-bot/source/Expired OTRS pending tagger

This task will tag files for deletion if they have been tagged with {{tl|OTRS pending}} longer than {{tl|OTRS backlog}} days..

///

/// This class will search for images that have been tagged as {{OTRS pending}} for more than {{OTRS backlog}}

/// days. It will tag the images with {{subst:npd}} and notify the uploader with {{subst:Di-no permission-notice-final}}.

///

public class ExpiredOtrsPendingTagger : BBotBase

{

///

/// Name of category to patrol

///

const String csOtrsPendingByDateCategoryName = "Category:Items pending OTRS confirmation of permission by date";

///

/// Maximum numberof

///

public int MaximumTagsPerRun { get; set; }

///

/// Constructor

///

public ExpiredOtrsPendingTagger()

{

MaximumTagsPerRun = Properties.Settings.Default.MaximumExpiredOtrsPendingTagsPerRun;

}

///

/// Gets the name for this job

///

///

public override string GetJobName()

{

return "Expired OTRS pending tagger";

}

///

/// This function will return the text of the last version of this page that either an OTRS user or an admin edited.

///

///

private String GetLastVersionByOtrsUser(Site site, String PageName, ref DateTime dtmModified)

{

System.Threading.Thread.Sleep(1000 * Properties.Settings.Default.CheckStopDelaySeconds);

PageList pl = new PageList(site);

pl.FillFromPageHistory(PageName, 20);

// Loop through the pages and find the last one an OTRS user or admin edited

foreach (Page p in pl)

{

if (IsUserOTRS(site, p.lastUser) || IsUserAdmin(site, p.lastUser))

{

p.LoadTextOnly();

dtmModified = p.timestamp;

System.Threading.Thread.Sleep(1000 * Properties.Settings.Default.CheckStopDelaySeconds);

return p.text;

}

}

return "";

}

///

/// Searches the text of a page to find the reqested tag

///

/// Revision text

///

///

///

public String GetTag(String strPageText, String strRegex)

{

Match match = Regex.Match(strPageText, strRegex, RegexOptions.IgnoreCase);

if (null != match && 0 < match.Length)

{

return strPageText.Substring(match.Index, match.Length);

}

return "";

}

///

/// The master function to perform the job

///

public void PerformTask()

{

// Always allow at least 30 days before deleting anything

const int ciMinimumBacklogDays = 30;

// On top of the backlog, allow 7 days so that we're not going to

const int ciGracePeriod = 7;

// Connect to Wikipedia

Site site = TryToConnect("https://en.wikipedia.org", Properties.Settings.Default.BotUserName, Properties.Settings.Default.BotPassword);

// Use a separate connection for our less-important API calls

Site site2 = TryToConnect("https://en.wikipedia.org", Properties.Settings.Default.BotUserName, Properties.Settings.Default.BotPassword);

// Log the start

if (UserspaceTest)

{

LogToEventLog(ref site, MessageType.Start, "B-Bot \"Expired OTRS pending tagger\" process now commencing IN TEST MODE.", null);

}

else

{

LogToEventLog(ref site, MessageType.Start, "B-Bot \"Expired OTRS pending tagger\" process now commencing.", null);

}

DateTime dtmBacklog = DateTime.Now.AddYears(-5);

String strBacklog = GetLastVersionByOtrsUser(site2, "Template:OTRS backlog", ref dtmBacklog);

SleepApiDelay();

Page pgUserspaceTest = new Page(site, Properties.Settings.Default.UserspaceTestPage);

if (UserspaceTest)

{

if (CallEditPage(site, pgUserspaceTest.title, "", "Initial header"))

{

pgUserspaceTest.text = "Now beginning expired OTRS pending tagger task on " + DateTime.Now.ToString() + " (local time) ...\r\n\r\n";

pgUserspaceTest.text += "

class=\"wikitable sortable\"\r\n
\r\n! Page !! Timestamp !! Proposed edit\r\n";

pgUserspaceTest.Save();

}

}

if (String.IsNullOrWhiteSpace(strBacklog))

{

LogToEventLog(ref site, MessageType.Error, "{{tl|OTRS backlog}} could not be read - no revision by an admin or OTRS user could be found. Aborting job.", null);

return;

}

strBacklog = RemoveComment(strBacklog, "", "");

int intBacklog = 0;

// Now, hopefully, what we have here is a number

try

{

intBacklog = System.Convert.ToInt32(strBacklog);

}

catch { }

if (0 >= intBacklog)

{

LogToEventLog(ref site, MessageType.Error, "{{tl|OTRS backlog}} could not be read as a positive integer. Please ensure that content outside of noinclude blocks is nothing but a number.", null);

return;

}

// Truncate the time

dtmBacklog = new DateTime(dtmBacklog.Year, dtmBacklog.Month, dtmBacklog.Day);

String strBacklogMessage = "The backlog is " + intBacklog.ToString() + " as of " + dtmBacklog.ToShortDateString() + ".";

// So now, we have a number of days. Subtract this from the date that the template was updated

dtmBacklog = dtmBacklog.AddDays(-1 * intBacklog);

strBacklogMessage += " (In other words, tickets prior to " + dtmBacklog.ToShortDateString() + " should have been processed.)";

// Now, provide a seven-day grace period

dtmBacklog = dtmBacklog.AddDays(-1 * ciGracePeriod);

// Require a minimum of 30 days

if (dtmBacklog.AddDays(ciMinimumBacklogDays) > DateTime.Now)

{

dtmBacklog = DateTime.Now.AddDays(-1 * ciMinimumBacklogDays);

strBacklogMessage += " However, we have a minimum time of " + ciMinimumBacklogDays.ToString() + " days before tagging and thus will process only images tagged prior to " + dtmBacklog.ToShortDateString() + ".";

}

else

{

intBacklog = (int)((DateTime.Now - dtmBacklog).TotalDays);

strBacklogMessage += " Including a grace period of " + ciGracePeriod.ToString() + " days to allow time to submit the ticket, we will process images tagged prior to " + dtmBacklog.ToShortDateString() +

", or, " + intBacklog.ToString() + " days.";

}

// Log our backlog

LogToEventLog(ref site, MessageType.Informational, strBacklogMessage, null);

// Grab the list of pages in the category

PageList pl = new PageList(site);

pl.FillAllFromCategory(csOtrsPendingByDateCategoryName);

SleepApiDelay();

int intTagsLeft = 0;

// Loop through the list

foreach (Page page in pl)

{

if (Abort)

{

break;

}

// Only process files

if (6 != page.GetNamespace())

{

continue;

}

// Load the page

page.Load();

SleepApiDelay();

// Ignore any pages already tagged with a deletion tag

bool blnTaggedForDeletion = false;

List listCategories = page.GetAllCategories();

SleepApiDelay();

foreach (String strCat in listCategories)

{

if (strCat == "Category:Candidates for speedy deletion"

strCat.StartsWith("Category:Wikipedia files missing permission")strCat == "Category:All non-free media"strCat == "Category:Items with OTRS permission confirmed")

{

blnTaggedForDeletion = true;

break;

}

}

if (blnTaggedForDeletion)

{

continue;

}

// We need to determine the date. If the OTRS pending tag has a date, use that.

// Otherwise, use the date of the last page revision.

String strOtrsPendingTag = GetTag(page.text, @"\{\{\s*otrs(\s

)pending([^\{^\}]*|)\}\}");

if (String.IsNullOrWhiteSpace(strOtrsPendingTag))

{

LogToEventLog(ref site2, MessageType.Error, "Error: could not find OTRS pending tag on :" + page.title + "", null);

continue;

}

// Split on the pipes

String[] arrParameters = strOtrsPendingTag.Split('|');

int intMonth = 0;

int intDay = 0;

int intYear = 0;

DateTime? dtmDate = null;

// Loop through our parameters

foreach (String param in arrParameters)

{

try

{

String[] arrNameValue = param.Split('=');

if (2 != arrNameValue.Length)

{

continue;

}

arrNameValue[1] = arrNameValue[1].Replace("}}", "").Trim();

// Look for month, day, year, and date

if (arrNameValue[0].Trim().ToLower() == "month")

{

intMonth = System.Convert.ToInt32(arrNameValue[1]);

}

else if (arrNameValue[0].Trim().ToLower() == "day")

{

intDay = System.Convert.ToInt32(arrNameValue[1]);

}

else if (arrNameValue[0].Trim().ToLower() == "year")

{

intYear = System.Convert.ToInt32(arrNameValue[1]);

}

else if (arrNameValue[0].Trim().ToLower() == "date")

{

try

{

dtmDate = DateTime.Parse(arrNameValue[1]);

}

catch

{

try

{

dtmDate = DateTime.ParseExact(arrNameValue[1], "hh:mm, dd MMMM yyyy (UTC)", System.Globalization.CultureInfo.InvariantCulture);

}

catch { }

}

}

}

catch{}

}

// Okay, now we should have a date maybe?

if (!dtmDate.HasValue)

{

if (0 < intMonth && 12 >= intMonth && 0 < intDay && 31 >= intDay && 2015 <= intYear)

{

try

{

dtmDate = new DateTime(intYear, intMonth, intDay);

}

catch{}

}

}

// If we get here, then we are just going to use the page revision date

if (!dtmDate.HasValue)

{

dtmDate = page.timestamp;

}

// Has our page expired?

if (dtmBacklog > dtmDate)

{

// Add the npd tag

if (CallEditPage(site, page.title, page.text, "{{subst:npd|source={{NoOTRS60|days={{subst:OTRS backlog}}}}}}\r\n" + page.text))

{

page.text = "{{subst:npd|source={{NoOTRS60|days={{subst:OTRS backlog}}}}}}\r\n" + page.text;

if (UserspaceTest)

{

pgUserspaceTest.text += "

\r\n| :" + page.title + "~~~~~
" + page.text.Substring(0, Math.Min(300, page.text.Length)) + "
\r\n";

pgUserspaceTest.Save(Properties.Settings.Default.NpdTagComment, false);

}

else

{

page.Save(page.text, Properties.Settings.Default.NpdTagComment, false);

}

}

if (Abort) { LogToEventLog(ref site, MessageType.Error, "I was ordered to abort.", null); break; }

intTagsLeft++;

// Sleep for our editing delay

SleepTaggingDelay();

// Determine the first contributor

PageList history = TryToFillFromPageHistory(ref site2, page.title);

if (0 < history.Count())

{

String strNotifyUser = history[history.Count() - 1].lastUser;

if (!String.IsNullOrWhiteSpace(strNotifyUser))

{

try

{

// Retrieve this user's talk page

Page pgUserTalkPage = new Page(site, "User talk:" + strNotifyUser);

pgUserTalkPage.Load();

SleepApiDelay();

// If it is a redirect, resolve it.

if (pgUserTalkPage.IsRedirect())

{

pgUserTalkPage.ResolveRedirect();

}

// Can we notify this user?

if (BotEditPermitted(pgUserTalkPage.text, Properties.Settings.Default.BotUserName, "npd"))

{

if (CallEditPage(site, pgUserTalkPage.title, pgUserTalkPage.text, pgUserTalkPage.text +

"\r\n{{subst:di-no permission-notice-final|" + page.title + "}} --~~~~"))

{

pgUserTalkPage.text += "\r\n{{subst:di-no permission-notice-final|" + page.title + "}} --~~~~";

if (UserspaceTest)

{

pgUserspaceTest.text += "

\r\n| :" + pgUserTalkPage.title + "~~~~~
" +

pgUserTalkPage.text.Substring(pgUserTalkPage.text.Length - Math.Min(300, pgUserTalkPage.text.Length)) + "

\r\n";

pgUserspaceTest.Save(String.Format(Properties.Settings.Default.NpdWarningTagComment, page.title), false);

}

else

{

pgUserTalkPage.Save(String.Format(Properties.Settings.Default.NpdWarningTagComment, page.title), false);

}

}

}

}

catch (Exception ex)

{

LogToEventLog(ref site, MessageType.Error, "Failed to notify :" + "User talk:" + strNotifyUser + " that the OTRS pending tag on [[:" +

page.title + "]] has expired.", ex);

}

}

}

if (Abort) { LogToEventLog(ref site, MessageType.Error, "I was ordered to abort.", null); break; }

// Sleep for our editing delay

System.Threading.Thread.Sleep(1000 * Properties.Settings.Default.TaggingEditDelaySeconds);

}

if (intTagsLeft >= MaximumTagsPerRun)

{

break;

}

SleepApiDelay();

}

if (UserspaceTest)

{

if (CallEditPage(site, pgUserspaceTest.title, "", "footer"))

{

pgUserspaceTest.text += "

\r\n";

pgUserspaceTest.Save();

}

}

// All done

LogToEventLog(ref site, MessageType.Finish, "B-Bot Expired OTRS pending tagger process done. Processed " + intTagsLeft.ToString() + " pages.", null);

}

}