User:31stCenturyBot/Source

This user is a bot operated by 31stCenturyMatt. For more information or to shut it off, please see UserWiki:31stCenturyBot.

=Source= I hereby place the following source code in the public domain. So, have fun.

31stCenturyBot was written in C# and relies on DotNetWikiBot to manipulate this wiki.

ReachChallenges.cs
 using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using System.Text.RegularExpressions; using DotNetWikiBot;

namespace _31stCenturyBot {

class Challenge {       public String Name; public String Requirement; public String CreditReward; public DateTime Date = DateTime.MinValue; public String DateString;

private String _image; public String Image {           get { return _image; } set {               Regex pattern = new Regex(@"(?:Bnet-challenge-icon(\d).png)|(?:/images/reachstats/challenges/(\d).png)", RegexOptions.Multiline | RegexOptions.IgnoreCase); if (pattern.IsMatch(value)) {                   Match match = pattern.Match(value); _image = match.Groups[1].Value; if(_image.Length <= 0) _image = match.Groups[2].Value; }               else _image = "-error-unrecognized-image";

}       }

public Challenge(String image, String name, String requirement, String creditReward) {           this.Image = image; this.Name = name; this.Requirement = requirement; this.CreditReward = creditReward; }

public override string ToString {           return String.Format("Image: {0}\r\nName: {1}\r\nRequirement: {2}\r\nCredit Reward: {3}\r\nDate: {4:M/d/yyyy}\r\nDate String: {5}\r\n",                Image, Name, Requirement, CreditReward, Date, DateString); }

//http://msdn.microsoft.com/en-us/library/ms173147%28VS.80%29.aspx public static bool operator ==(Challenge a, Challenge b)       { // If both are null, or both are same instance, return true. if (System.Object.ReferenceEquals(a, b)) {               return true; }

// If one is null, but not both, return false. if (((object)a == null) || ((object)b == null)) {               return false; }

// Return true if the fields match: return (a.Name == b.Name && a.Requirement == b.Requirement && a.CreditReward == b.CreditReward); }

public static bool operator !=(Challenge a, Challenge b)       { return !(a == b); }

public override bool Equals(object obj) {           if (obj is Challenge) return ((Challenge)obj) == this; else return false; }

public override int GetHashCode {           return base.GetHashCode; }

}

class ReachChallenges {       public delegate void LogDelegate(String line); protected LogDelegate logDelegate;

private static Regex BNETWEEKLY = new Regex(@" Weekly Challenge: [\s\S]*?[\s\S]*?([^<]+) [\s\S]*? \s*([0-9,]+)\s*cR"); private static Regex BNETALL = new Regex(@"]*?src=['""]([^'""]+)['""][^>]*?>\s*]*>\s* ([^<]+) \s*([^<]+) [\s\S]*? \s*([0-9,]+)\s*cR", RegexOptions.Multiline); private static Regex WIKISECTION = new Regex(@"(===?.*Challenges===?[\s\S]*?)\{\|align[\s\S]*?Weekly C.*([\s\S]*?)!colspan=.*?Daily C.*([\s\S]*?)\|\}"); private static Regex WIKIROW = new Regex(@"\|-.*\n\|\[\[File\:([^\|\]]+).*\]\]\n\|(?:)?(.*?)(?:)?\n\|[^\|]*\|?(.*)\n\|(\d+) ?cR\n\|(.*)", RegexOptions.Multiline);

private Challenge bNetWeekly; private List bNetDaily = new List;

private Challenge wikiWeekly; private List wikiDaily = new List;

public ReachChallenges(LogDelegate logDelegate) {           this.logDelegate = logDelegate; }

public void Run {           StringBuilder log = new StringBuilder; log.AppendFormat("Begin: {0:yyyy-MM-dd HH:mm:ss UTC}. ", DateTime.UtcNow); logDelegate(String.Format("Begin: {0:yyyy-MM-dd HH:mm:ss UTC}.\r\n", DateTime.UtcNow));

Match match; MatchCollection matches; int newDayHour = DateTime.Now.IsDaylightSavingTime ? 10 : 11;

Site site; try {               site = new Site("http://www.halopedian.com", "31stCenturyBot", "I'm not giving my password to a machine!"); }           catch (Exception ex) {               logDelegate("Unhandled exception. Unable to connect to Halopedia. Aborting.\r\n"); logDelegate(ex.StackTrace); return; }

#region Parse Bungie.net try {               HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://www.bungie.net/stats/reach/globalchallenges.aspx"); request.Method = "GET"; request.Headers["Accept-Encoding"] = "gzip,deflate"; request.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;

HttpWebResponse response = (HttpWebResponse)request.GetResponse; String html = new StreamReader(response.GetResponseStream).ReadToEnd; response.Close;

match = BNETWEEKLY.Match(html); bNetWeekly = new Challenge(match.Groups[1].Value, match.Groups[2].Value, match.Groups[3].Value, match.Groups[4].Value); if (bNetWeekly.Image == "-error-unrecognized-image" || int.Parse(bNetWeekly.Image) > 4) {                   log.AppendFormat("Unrecognized challenge icon: {0}. ", match.Groups[1].Value); logDelegate(String.Format("Unrecognized challenge icon: {0}.\r\n", match.Groups[1].Value)); }

DateTime today = DateTime.UtcNow; if (today.Hour < newDayHour) today = today.AddDays(-1);

DateTime tempStart, tempEnd; switch (today.DayOfWeek) {                   case DayOfWeek.Monday: tempStart = today; break; case DayOfWeek.Tuesday: tempStart = today.AddDays(-1); break; case DayOfWeek.Wednesday: tempStart = today.AddDays(-2); break; case DayOfWeek.Thursday: tempStart = today.AddDays(-3); break; case DayOfWeek.Friday: tempStart = today.AddDays(-4); break; case DayOfWeek.Saturday: tempStart = today.AddDays(-5); break; case DayOfWeek.Sunday: default: tempStart = today.AddDays(-6); break; }               tempEnd = tempStart.AddDays(6); if (tempStart.Month == tempEnd.Month) bNetWeekly.DateString = String.Format("{0:MMMM d}-{1:d, yyyy}", tempStart, tempEnd); else if (tempStart.Month == 12 && tempEnd.Month == 1) bNetWeekly.DateString = String.Format("{0:MMMM d, yyyy}-{1:MMMM d, yyyy}", tempStart, tempEnd); else bNetWeekly.DateString = String.Format("{0:MMMM d}-{1:MMMM d, yyyy}", tempStart, tempEnd);

matches = BNETALL.Matches(html); foreach (Match m in matches) {                   Challenge temp = new Challenge(m.Groups[1].Value, m.Groups[2].Value, m.Groups[3].Value, m.Groups[4].Value); temp.Date = today;

if (temp != bNetWeekly) bNetDaily.Add(temp); }               log.AppendFormat("Parsed 1 weekly and {0} daily challenges from Bungie.net. ", bNetDaily.Count); logDelegate(String.Format("Parsed 1 weekly and {0} daily challenges from Bungie.net.\r\n", bNetDaily.Count)); }           catch (Exception ex) {               log.Append("Unhandled exception. Unable to retrieve data from Bungie.net. Aborting. "); logDelegate("Unhandled exception. Unable to retrieve data from Bungie.net. Aborting.\r\n"); logDelegate(ex.StackTrace); WriteLog(site, log.ToString); return; }           #endregion

Page p = new Page(site, "Challenges"); p.LoadEx;

#region Parse and update Challenges match = WIKISECTION.Match(p.text); Match match2 = WIKIROW.Match(match.Groups[2].Value); wikiWeekly = new Challenge(match2.Groups[1].Value, match2.Groups[2].Value, match2.Groups[3].Value, match2.Groups[4].Value); wikiWeekly.DateString = match2.Groups[5].Value;

matches = WIKIROW.Matches(match.Groups[3].Value); foreach (Match m in matches) {               Challenge temp = new Challenge(m.Groups[1].Value, m.Groups[2].Value, m.Groups[3].Value, m.Groups[4].Value); temp.DateString = m.Groups[5].Value; temp.Date = DateTime.Parse(temp.DateString); wikiDaily.Add(temp); }

int identical = 0; foreach (Challenge c in bNetDaily) {               if (wikiDaily.Contains(c)) identical++; }

if (identical == bNetDaily.Count && wikiWeekly == bNetWeekly) {               log.Append("Nothing to update on Challenges. "); logDelegate("Nothing to update on Challenges.\r\n"); }           else {               p.text = WIKISECTION.Replace(p.text, match.Groups[1] + GenerateTable(bNetWeekly, bNetDaily)); p.Save("Updated challenge table.", true); log.Append("Updated Challenges. "); logDelegate("Updated Challenges.\r\n"); }           #endregion

String result = InsertChallenges(site, String.Format("Challenges/{0:yyyy-MMMM}", bNetDaily[0].Date), bNetDaily[0].Date, bNetWeekly, bNetDaily); log.AppendFormat(result, " "); logDelegate(String.Format(result, "\r\n"));

if (!result.Contains("Nothing to update")) {               //weekly challenge straddles two months Regex r = new Regex(@"\w+ \d+(?:, \d+)?-(\w+) \d+, \d+"); if (r.IsMatch(bNetWeekly.DateString) && r.Match(bNetWeekly.DateString).Groups[1].Value != DateTime.UtcNow.ToString("MMMM") && (DateTime.UtcNow.DayOfWeek != DayOfWeek.Monday || DateTime.UtcNow.Hour >= newDayHour)) {                   result = InsertChallenges(site, String.Format("Challenges/{0:yyyy-MMMM}", bNetDaily[0].Date.AddMonths(1)), bNetDaily[0].Date.AddMonths(1), bNetWeekly, new List); log.AppendFormat(result, " "); logDelegate(String.Format(result, "\r\n")); }           }

log.AppendFormat("End: {0:yyyy-MM-dd HH:mm:ss UTC}. ", DateTime.UtcNow); logDelegate(String.Format("End: {0:yyyy-MM-dd HH:mm:ss UTC}.", DateTime.UtcNow));

WriteLog(site, log.ToString); }

private String GenerateTable(Challenge weekly, List daily) {           List temp = new List; temp.Add(weekly); return GenerateTable(temp, daily); }

private String GenerateTable(List weekly, List daily) {           String rowFormat = "|-align=\"center\"{5}\n|\n|{1}\n|style=\"text-align:left\"|{2}\n|{3} cR\n|{4}\n"; StringBuilder sb = new StringBuilder; sb.Append("{|align=\"center\" cellpadding=\"3\" border=\"1\"\n|-align=\"center\"\n!Icon\n!Name\n!Requirement\n!Credit Reward\n!Date\n|-align=\"center\"\n!colspan=\"5\"|Weekly Challenges\n"); bool prevRowIsAlternate = true; String prevRowDate = ""; foreach (Challenge c in weekly) {               if (c.DateString != prevRowDate) {                   prevRowIsAlternate = !prevRowIsAlternate; prevRowDate = c.DateString; }

if (prevRowIsAlternate) sb.AppendFormat(rowFormat, c.Image, c.Name, c.Requirement, c.CreditReward, c.DateString, " style=\"background-color:#c1ecf2\""); else sb.AppendFormat(rowFormat, c.Image, c.Name, c.Requirement, c.CreditReward, c.DateString, ""); }           sb.Append("|-align=\"center\"\n!colspan=\"5\"|Daily Challenges\n"); prevRowIsAlternate = true; prevRowDate = ""; foreach (Challenge c in daily) {               c.DateString = c.Date.ToString("MMMM d, yyyy"); if (c.DateString != prevRowDate) {                   prevRowIsAlternate = !prevRowIsAlternate; prevRowDate = c.DateString; }               if(prevRowIsAlternate) sb.AppendFormat(rowFormat, c.Image, c.Name, c.Requirement, c.CreditReward, c.DateString, " style=\"background-color:#c1ecf2\""); else sb.AppendFormat(rowFormat, c.Image, c.Name, c.Requirement, c.CreditReward, c.DateString, ""); }           sb.Append("|}"); return sb.ToString; }

private void GenerateNewPage(Site s, Page p, DateTime start, DateTime end) {           p.text = String.Format("This article contains the list of challenges in Halo: Reach for {0:MMMM yyyy}. For current challenges see the main article or Bungie.net.\n\n==Challenges==\n\n\n\n", end); p.Save("Creating page for new month.", false);

Page template = new Page(s, "Template:Challenge-Archive"); template.LoadEx; StringBuilder sb = new StringBuilder; sb.Append(" "); template.text = sb.ToString; template.Save("Updated list of pages.", true); }

private String InsertChallenges(Site site, String page, DateTime month, Challenge weekly, List<Challenge> daily) {           StringBuilder log = new StringBuilder; List<Challenge> wikiMonthWeekly = new List<Challenge>; List<Challenge> wikiMonthDaily = new List<Challenge>; Page p = new Page(site, page); p.LoadEx; if (!p.Exists || p.text.Trim.Length <= 0) {               GenerateNewPage(site, p, new DateTime(2010, 9, 1), month); log.AppendFormat("Created new page for {0:MMMM yyyy}.", month); }

Match match = WIKISECTION.Match(p.text);

MatchCollection matches = WIKIROW.Matches(match.Groups[2].Value); foreach (Match m in matches) {               Challenge temp = new Challenge(m.Groups[1].Value, m.Groups[2].Value, m.Groups[3].Value, m.Groups[4].Value); temp.DateString = m.Groups[5].Value; wikiMonthWeekly.Add(temp); }

List<Challenge> monthMostRecent = new List<Challenge>; matches = WIKIROW.Matches(match.Groups[3].Value); foreach (Match m in matches) {               Challenge temp = new Challenge(m.Groups[1].Value, m.Groups[2].Value, m.Groups[3].Value, m.Groups[4].Value); temp.DateString = m.Groups[5].Value; temp.Date = DateTime.Parse(temp.DateString); wikiMonthDaily.Add(temp); if (monthMostRecent.Count <= 0) monthMostRecent.Add(temp); else if (temp.Date.Day > monthMostRecent[0].Date.Day) {                   monthMostRecent.Clear; monthMostRecent.Add(temp); }               else if (temp.Date.Day == monthMostRecent[0].Date.Day) monthMostRecent.Add(temp); }

int identical = 0; foreach (Challenge c in daily) {               if (monthMostRecent.Contains(c)) identical++; }

if (identical == daily.Count && wikiMonthWeekly.Contains(weekly)) log.AppendFormat("Nothing to update on {0}.", page); else {               for (int i = daily.Count - 1; i >= 0; i--) wikiMonthDaily.Insert(0, daily[i]);

if (!wikiMonthWeekly.Contains(weekly)) wikiMonthWeekly.Insert(0, weekly);

p.text = WIKISECTION.Replace(p.text, match.Groups[1] + GenerateTable(wikiMonthWeekly, wikiMonthDaily)); p.Save(String.Format("Updated challenges for {0:MMMM yyyy}.", month), true); log.AppendFormat("Updated {0}.", page); }

return log.ToString; }

private void WriteLog(Site site, String log) {           try {               Page p = new Page(site, "User:31stCenturyBot/Log"); p.LoadEx; p.text = new Regex(@"(===?.*Log.*===?[\s\S]*?)(\*)", RegexOptions.Multiline | RegexOptions.IgnoreCase).Replace(p.text, String.Format("$1*{0}\n$2", log.ToString)); p.Save("Updated log.", true); }           catch (Exception ex) {               logDelegate("Failed to save log to Halopedia.\r\n"); logDelegate(ex.StackTrace + "\r\n"); }       }    } }

Usage
private void Log(String s) { txtLog.Text += s; }

private void btnGo_Click(object sender, EventArgs e) { ReachChallenges procedure = new ReachChallenges(new ReachChallenges.LogDelegate(this.Log)); procedure.Run; StreamWriter sw = new StreamWriter(@"C:\...\Log.txt", true); sw.WriteLine("========================================================================"); sw.WriteLine(txtLog.Text); sw.WriteLine("========================================================================"); sw.Close; }