386 lines
9.9 KiB
C#
386 lines
9.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Web;
|
|
using System.Web.Script.Serialization;
|
|
using System.Xml;
|
|
using Rss;
|
|
|
|
public class Twitterbot
|
|
{
|
|
public const string TwitterUrl = "http://twitter.com/statuses/update.xml";
|
|
public const string TinyUrl = "http://urlenco.de/PostJSON.aspx?encode=";
|
|
public const int TwitterMax = 3; // maximum number of feed updates to pull
|
|
public const int HistoryMax = 10; // maximum number of backlogged feed items to store
|
|
|
|
public static void Main(string[] args)
|
|
{
|
|
bool slowAtNight = false;
|
|
|
|
/*
|
|
* Workaround for the 417 error found here:
|
|
* http://groups.google.com/group/twitter-development-talk/browse_thread/thread/7c67ff1a2407dee7
|
|
*/
|
|
System.Net.ServicePointManager.Expect100Continue = false;
|
|
|
|
if ( (args.Length < 3) || (args.Length > 4) )
|
|
{
|
|
Console.WriteLine("Twitterbot.exe <feed file> <minutes to sleep> <night time slowdown>");
|
|
Console.WriteLine("Usage:");
|
|
Console.WriteLine("mono Twitterbot.exe feeds.xml 30 yes");
|
|
|
|
Console.WriteLine("");
|
|
Console.WriteLine("Enabling night time slowdown will cause the Twitterbot to double its");
|
|
Console.WriteLine("sleep interval between the hours of 12:00 a.m. and 6 a.m.");
|
|
return;
|
|
}
|
|
|
|
string feedFile = args[0];
|
|
int sleep = Convert.ToInt32(args[1]);
|
|
|
|
if ( (args[2] == "yes") || (args[2] == "y") )
|
|
slowAtNight = true;
|
|
|
|
List<Feed> feedList = new List<Feed>();
|
|
|
|
Console.WriteLine("Reading {0} and looping {1} minutes", feedFile, sleep);
|
|
|
|
/*
|
|
* The following block is quite basic, open the feed xml file and
|
|
* generate a List of Feed objects.
|
|
*/
|
|
////////////////////////////////////////////////////////////////////////
|
|
XmlDocument feedXml = new XmlDocument();
|
|
XmlNodeList feeds;
|
|
FileStream stream;
|
|
|
|
if (File.Exists(feedFile))
|
|
{
|
|
stream = File.OpenRead(feedFile);
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("File {0} not found", feedFile);
|
|
return;
|
|
}
|
|
|
|
feedXml.Load(stream);
|
|
feeds = feedXml.GetElementsByTagName("feed");
|
|
|
|
foreach (XmlNode feed in feeds)
|
|
{
|
|
string name = feed.Attributes["name"].InnerText;
|
|
string url = feed.Attributes["url"].InnerText;
|
|
string twitter = feed.Attributes["twitter"].InnerText;
|
|
string pass = feed.Attributes["password"].InnerText;
|
|
|
|
feedList.Add(new Feed(name, url, twitter, pass));
|
|
}
|
|
|
|
stream.Close();
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/*
|
|
* Our core runloop will iterate every 'sleep' minutes, first udpating
|
|
* each one of our feeds, checking for duplicates, posting new entries
|
|
* to the Feed's twitter account, and finally backlogging entries
|
|
*/
|
|
////////////////////////////////////////////////////////////////////////
|
|
do
|
|
{
|
|
foreach (Feed feed in feedList)
|
|
{
|
|
Console.WriteLine("Reading {0}", feed.Name);
|
|
try
|
|
{
|
|
HttpWebRequest rssRequest = GenerateGetOrPostRequest(feed.Url, "GET", null, null, null);
|
|
feed.Rss = RssFeed.Read(rssRequest);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("RSS.NET received an exception!{0}{1}",
|
|
Environment.NewLine, ex);
|
|
continue;
|
|
}
|
|
|
|
List<RssItem> tempList = new List<RssItem>();
|
|
|
|
foreach (RssChannel channel in feed.Rss.Channels)
|
|
{
|
|
int count = 0;
|
|
|
|
foreach (RssItem item in channel.Items)
|
|
{
|
|
if (count == TwitterMax)
|
|
break;
|
|
if (feed.LastItems.Count > 0)
|
|
{
|
|
bool found = false;
|
|
foreach (RssItem lastItem in feed.LastItems)
|
|
{
|
|
if (item.Title == lastItem.Title)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
tempList.Add(item);
|
|
}
|
|
else
|
|
{
|
|
tempList.Add(item);
|
|
}
|
|
++count;
|
|
}
|
|
break; // on the off-chance there is more than one channel
|
|
}
|
|
|
|
foreach (RssItem item in tempList)
|
|
{
|
|
// Prepare to post to twitter
|
|
string post = PrepareTwitterStatus(item);
|
|
Console.WriteLine("[{0}] Preparing post: {1}", DateTime.Now, post);
|
|
|
|
if (!PostToTwitter(feed, post))
|
|
Console.WriteLine("[{0}] Failed to post to twitter!", DateTime.Now);
|
|
|
|
// Patiently count to five to reduce spammage
|
|
Thread.Sleep(5000);
|
|
}
|
|
// Add our added items to the backlog list
|
|
feed.AddItems(tempList);
|
|
}
|
|
Console.WriteLine("--------");
|
|
|
|
// If we're slowing down at night, check to see if we're between 12:00 and 6:00 exclusive
|
|
if ( (slowAtNight) && (DateTime.Now.Hour >= 0) && (DateTime.Now.Hour < 6) )
|
|
{
|
|
int slowSleep = (sleep * 2);
|
|
Console.WriteLine("Doubling interval of {0} minute(s) to {1} minute(s) for night time hours", sleep, slowSleep);
|
|
Thread.Sleep((slowSleep * 1000)*60);
|
|
}
|
|
else
|
|
{
|
|
Thread.Sleep((sleep * 1000)*60);
|
|
}
|
|
} while (true);
|
|
////////////////////////////////////////////////////////////////////////
|
|
////////////////////////////////////////////////////////////////////////
|
|
}
|
|
|
|
|
|
public static bool PostToTwitter(Feed feed, string post)
|
|
{
|
|
HttpWebRequest request = null;
|
|
string returnString = string.Empty;
|
|
HttpWebResponse response = null;
|
|
|
|
try
|
|
{
|
|
request = GenerateGetOrPostRequest(TwitterUrl, "POST", string.Format("status={0}", post),
|
|
feed.TwitterName, feed.TwitterPass);
|
|
response = (HttpWebResponse)request.GetResponse();
|
|
Stream responseStream = response.GetResponseStream( );
|
|
StreamReader reader = new StreamReader(responseStream,Encoding.UTF8);
|
|
|
|
try
|
|
{
|
|
returnString = reader.ReadToEnd( );
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("Received exception! {0}{1}", Environment.NewLine, ex);
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
if (reader != null)
|
|
reader.Close();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("Received exception! {0}{1}", Environment.NewLine, ex);
|
|
return false;
|
|
}
|
|
finally
|
|
{
|
|
if (response != null)
|
|
response.Close();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
public static string PrepareTwitterStatus(RssItem item)
|
|
{
|
|
string postString;
|
|
string titleString = string.Empty;
|
|
bool tainted = false;
|
|
|
|
// This wee little bit of information should ensure we don't add any ' shit into the string
|
|
foreach (char oneChar in item.Title.ToCharArray())
|
|
{
|
|
if ( (oneChar == ';') && (tainted) )
|
|
{
|
|
tainted = false;
|
|
continue;
|
|
}
|
|
else if (oneChar == '&')
|
|
{
|
|
tainted = true;
|
|
continue;
|
|
}
|
|
else if (oneChar == ';')
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (!tainted)
|
|
titleString += oneChar;
|
|
}
|
|
}
|
|
|
|
if (item.Title.Length > 124)
|
|
{
|
|
postString = titleString.Substring(0,124);
|
|
postString += ".. ";
|
|
}
|
|
else
|
|
{
|
|
postString = titleString;
|
|
}
|
|
|
|
Console.WriteLine(item.Link);
|
|
postString += string.Format(": {0}", FetchTinyUrl(item.Link));
|
|
|
|
return postString;
|
|
}
|
|
|
|
|
|
public static string FetchTinyUrl(Uri longUrl)
|
|
{
|
|
string tinyurl;
|
|
string target_url = HttpUtility.UrlEncode(longUrl.ToString());
|
|
|
|
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(string.Format("{0}{1}", TinyUrl, target_url));
|
|
HttpWebResponse response = null;
|
|
|
|
try
|
|
{
|
|
response = (HttpWebResponse)request.GetResponse();
|
|
Stream responseStream = response.GetResponseStream( );
|
|
StreamReader reader = new StreamReader(responseStream, Encoding.UTF8);
|
|
|
|
try
|
|
{
|
|
tinyurl = reader.ReadToEnd();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("Received exception! {0}{1}", Environment.NewLine, ex);
|
|
return string.Empty;
|
|
}
|
|
finally
|
|
{
|
|
if (reader != null)
|
|
reader.Close();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("Received exception! {0}{1}", Environment.NewLine, ex);
|
|
return string.Empty;
|
|
}
|
|
finally
|
|
{
|
|
if (response != null)
|
|
response.Close();
|
|
}
|
|
|
|
JavaScriptSerializer serializer = new JavaScriptSerializer();
|
|
Dictionary<string, string> results = serializer.Deserialize<Dictionary<string, string>>(tinyurl);
|
|
return results["encoded"];
|
|
}
|
|
|
|
|
|
public static HttpWebRequest GenerateGetOrPostRequest(string uriString, string method, string postData, string user, string pass)
|
|
{
|
|
if ((method.ToUpper() != "GET") && (method.ToUpper() != "POST"))
|
|
throw new ArgumentException(method + " is not a valid method. Use GET or POST.","method");
|
|
|
|
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(uriString);
|
|
httpRequest.Method = method;
|
|
httpRequest.UserAgent = "Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.1.13)";
|
|
|
|
if (method.ToUpper() =="POST")
|
|
{
|
|
if ( (user != null) && (pass != null) )
|
|
{
|
|
httpRequest.Credentials = new NetworkCredential(user, pass);
|
|
}
|
|
|
|
// Get the bytes for the request, should be pre-escaped
|
|
byte[] bytes = Encoding.UTF8.GetBytes(postData);
|
|
|
|
// Set the content type of the data being posted.
|
|
httpRequest.ContentType = "application/x-www-form-urlencoded";
|
|
|
|
// Set the content length of the string being posted.
|
|
httpRequest.ContentLength = bytes.Length;
|
|
|
|
Stream requestStream = httpRequest.GetRequestStream();
|
|
requestStream.Write(bytes,0,bytes.Length);
|
|
// Done updating for POST so close the stream
|
|
requestStream.Close();
|
|
}
|
|
|
|
return httpRequest;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public class Feed
|
|
{
|
|
public string Name;
|
|
public string Url;
|
|
public string TwitterName;
|
|
public string TwitterPass;
|
|
public RssFeed Rss;
|
|
public List<RssItem> LastItems;
|
|
|
|
public Feed(string name, string url, string twitter, string pass)
|
|
{
|
|
Name = name;
|
|
Url = url;
|
|
TwitterName = twitter;
|
|
TwitterPass = pass;
|
|
LastItems = new List<RssItem>(10);
|
|
}
|
|
|
|
public void AddItems(List<RssItem> items)
|
|
{
|
|
// If we've exceeded our max, remove items.Count from the front of the list
|
|
// and add items to the end of the list
|
|
if (LastItems.Count > Twitterbot.HistoryMax)
|
|
{
|
|
for (int i = 0; i < items.Count; ++i)
|
|
{
|
|
LastItems.RemoveAt(i);
|
|
}
|
|
}
|
|
|
|
LastItems.AddRange(items);
|
|
}
|
|
}
|