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 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 + " | ~~~~~ | " +\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 += " |
pgUserspaceTest.Save();
}
}
// All done
LogToEventLog(ref site, MessageType.Finish, "B-Bot Expired OTRS pending tagger process done. Processed " + intTagsLeft.ToString() + " pages.", null);
}
}