User:B-bot/source/Expired OTRS pending tagger
Appearance
< User:B-bot | source
This task will tag files for deletion if they have been tagged with {{OTRS pending}} longer than {{OTRS backlog}} days..
/// <summary>
/// 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}}.
/// </summary>
public class ExpiredOtrsPendingTagger : BBotBase
{
/// <summary>
/// Name of category to patrol
/// </summary>
const String csOtrsPendingByDateCategoryName = "Category:Items pending OTRS confirmation of permission by date";
/// <summary>
/// Maximum numberof
/// </summary>
public int MaximumTagsPerRun { get; set; }
/// <summary>
/// Constructor
/// </summary>
public ExpiredOtrsPendingTagger()
{
MaximumTagsPerRun = Properties.Settings.Default.MaximumExpiredOtrsPendingTagsPerRun;
}
/// <summary>
/// Gets the name for this job
/// </summary>
/// <returns></returns>
public override string GetJobName()
{
return "Expired OTRS pending tagger";
}
/// <summary>
/// This function will return the text of the last version of this page that either an OTRS user or an admin edited.
/// </summary>
/// <param name="PageName"></param>
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 "";
}
/// <summary>
/// Searches the text of a page to find the reqested tag
/// </summary>
/// <param name="strPageText">Revision text</param>
/// <param name="strRegex"></param>
/// <returns></returns>
/// <remarks></remarks>
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 "";
}
/// <summary>
/// The master function to perform the job
/// </summary>
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 [[template:OTRS pending|OTRS pending]] tagger\" process now commencing <font color='red'>'''IN TEST MODE'''</font>.", null);
}
else
{
LogToEventLog(ref site, MessageType.Start, "B-Bot \"Expired [[template:OTRS pending|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, "<noinclude>", "</noinclude>");
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 [[Template:OTRS backlog|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<String> 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 + "]] || ~~~~~ || <pre>" + page.text.Substring(0, Math.Min(300, page.text.Length)) + "</pre>\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 + "]] || ~~~~~ || <pre>" +
pgUserTalkPage.text.Substring(pgUserTalkPage.text.Length - Math.Min(300, pgUserTalkPage.text.Length)) + "</pre>\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 [[template:OTRS pending|OTRS pending]] tagger process done. Processed " + intTagsLeft.ToString() + " pages.", null);
}
}