Index: Witty/Witty/Witty.csproj
===================================================================
--- Witty/Witty/Witty.csproj (revision 349)
+++ Witty/Witty/Witty.csproj (working copy)
@@ -225,6 +225,10 @@
{EA9BEE27-11D2-4346-83D9-9A056282B7B9}
Common
+
+ {580176B8-7199-406C-AFFB-C8B81450AEDC}
+ IdenticaLib
+
{6CB55919-6C11-43A1-8769-57461BE5B0AE}
TwitterLib
Index: Witty/Witty/MainWindow.xaml.cs
===================================================================
--- Witty/Witty/MainWindow.xaml.cs (revision 349)
+++ Witty/Witty/MainWindow.xaml.cs (working copy)
@@ -14,8 +14,10 @@
using TwitterLib.Utilities;
using Witty.ClickOnce;
using Witty.Properties;
+using IdenticaLib;
using System.Windows.Documents;
+
namespace Witty
{
public partial class MainWindow
@@ -96,9 +98,10 @@
System.Security.SecureString password = TwitterNet.DecryptString(AppSettings.Password);
+
// Jason Follas: Reworked Web Proxy - don't need to explicitly pass into TwitterNet ctor
//twitter = new TwitterNet(AppSettings.Username, password, WebProxyHelper.GetConfiguredWebProxy());
- twitter = new TwitterNet(AppSettings.Username, password);
+ twitter = new IdenticaAPI(AppSettings.Username, password);
// Jason Follas: Twitter proxy servers, anyone? (Network Nazis who block twitter.com annoy me)
twitter.TwitterServerUrl = AppSettings.TwitterHost;
@@ -178,10 +181,10 @@
private delegate void NoArgDelegate();
private delegate void OneArgDelegate(TweetCollection arg);
private delegate void OneStringArgDelegate(string arg);
- private delegate void AddTweetUpdateDelegate(Tweet arg);
+ private delegate void AddTweetUpdateDelegate(ITweet arg);
private delegate void MessagesDelegate(DirectMessageCollection arg);
private delegate void SendMessageDelegate(string user, string text);
- private delegate void LoginDelegate(User arg);
+ private delegate void LoginDelegate(IUser arg);
private delegate void DeleteTweetDelegate(double id);
// Settings used by the application
@@ -224,14 +227,14 @@
private int popupCount = 0;
- internal Tweet SelectedTweet
+ internal ITweet SelectedTweet
{
get
{
- Tweet selectedTweet = null;
+ ITweet selectedTweet = null;
if (this.currentView == CurrentView.Replies)
{
- if (null != RepliesListBox.SelectedItem) selectedTweet = (Tweet)RepliesListBox.SelectedItem;
+ if (null != RepliesListBox.SelectedItem) selectedTweet = (ITweet)RepliesListBox.SelectedItem;
}
else if (this.currentView == CurrentView.Messages)
{
@@ -239,7 +242,7 @@
}
else
{
- if (null != TweetsListBox.SelectedItem) selectedTweet = (Tweet)TweetsListBox.SelectedItem;
+ if (null != TweetsListBox.SelectedItem) selectedTweet = (ITweet)TweetsListBox.SelectedItem;
}
return selectedTweet;
}
@@ -317,7 +320,7 @@
// Add the new tweets
for (int i = newTweets.Count - 1; i >= 0; i--)
{
- Tweet tweet = newTweets[i];
+ ITweet tweet = newTweets[i];
if (!tweets.Contains(tweet))
{
tweets.Insert(0, tweet);
@@ -392,7 +395,7 @@
else
{
int index = 0;
- foreach (Tweet tweet in newTweets)
+ foreach (ITweet tweet in newTweets)
{
Popup p = new Popup(tweet, index++);
p.FadeOutFinished += new FadeOutFinishedDelegate(RemovePopup);
@@ -408,7 +411,7 @@
private static string BuiltNewTweetMessage(TweetCollection newTweets)
{
string message = string.Format("You have {0} new tweets!\n", newTweets.Count);
- foreach (Tweet tweet in newTweets)
+ foreach (ITweet tweet in newTweets)
{
message += " " + tweet.User.ScreenName;
}
@@ -440,7 +443,7 @@
}
else
{
- foreach (Tweet tweet in newTweets)
+ foreach (ITweet tweet in newTweets)
{
SnarlInterface.SendMessage(string.Format("New Tweet from {0}", tweet.User.ScreenName), string.Format("{0}\n\n{1}", tweet.Text, tweet.RelativeTime), "", 4);
}
@@ -455,7 +458,7 @@
private static void UpdateExistingTweets(TweetCollection oldTweets)
{
// Update existing tweets
- foreach (Tweet tweet in oldTweets)
+ foreach (ITweet tweet in oldTweets)
{
tweet.IsNew = false;
tweet.UpdateRelativeTime();
@@ -489,7 +492,7 @@
TinyUrlHelper tinyUrls = new TinyUrlHelper();
tweetText = tinyUrls.ConvertUrlsToTinyUrls(tweetText);
}
- Tweet tweet = twitter.AddTweet(tweetText); ;
+ ITweet tweet = twitter.AddTweet(tweetText); ;
// Schedule the update function in the UI thread.
LayoutRoot.Dispatcher.BeginInvoke(
@@ -514,7 +517,7 @@
}
- private void UpdatePostUserInterface(Tweet newlyAdded)
+ private void UpdatePostUserInterface(ITweet newlyAdded)
{
if (newlyAdded != null)
{
@@ -607,7 +610,7 @@
for (int i = newReplies.Count - 1; i >= 0; i--)
{
- Tweet reply = newReplies[i];
+ ITweet reply = newReplies[i];
if (!replies.Contains(reply))
{
replies.Insert(0, reply);
@@ -807,7 +810,7 @@
fakeTweets[0].Id = -1;
fakeTweets[0].Text = userNotFoundEx.Message;
fakeTweets[0].Source = "Witty Error Handler";
- fakeTweets[0].User = new User();
+ fakeTweets[0].User = new TwitterLib.User();
fakeTweets[0].User.ScreenName = "@" + userNotFoundEx.UserId;
fakeTweets[0].User.Description = userNotFoundEx.Message;
@@ -842,7 +845,7 @@
for (int i = newTweets.Count - 1; i >= 0; i--)
{
- Tweet tweet = newTweets[i];
+ ITweet tweet = newTweets[i];
if (!userTweets.Contains(tweet))
{
userTweets.Insert(0, tweet);
@@ -892,7 +895,7 @@
}
}
- private void UpdatePostLoginInterface(User user)
+ private void UpdatePostLoginInterface(IUser user)
{
App.LoggedInUser = user;
if (App.LoggedInUser != null)
@@ -928,9 +931,10 @@
private void LoginControl_Login(object sender, RoutedEventArgs e)
{
+
// Jason Follas: Reworked Web Proxy - don't need to explicitly pass into TwitterNet ctor
//twitter = new TwitterNet(AppSettings.Username, TwitterNet.DecryptString(AppSettings.Password), WebProxyHelper.GetConfiguredWebProxy());
- twitter = new TwitterNet(AppSettings.Username, TwitterNet.DecryptString(AppSettings.Password));
+ twitter = new IdenticaAPI(AppSettings.Username, TwitterNet.DecryptString(AppSettings.Password));
// Jason Follas: Twitter proxy servers, anyone? (Network Nazis who block twitter.com annoy me)
twitter.TwitterServerUrl = AppSettings.TwitterHost;
@@ -1353,7 +1357,7 @@
{
if (listbox.SelectedItem != null && currentView != CurrentView.User)
{
- Tweet tweet = (Tweet)listbox.SelectedItem;
+ ITweet tweet = (ITweet)listbox.SelectedItem;
//System.Diagnostics.Process.Start(tweet.User.TwitterUrl);
DelegateUserTimelineFetch(tweet.User.ScreenName);
}
@@ -1578,7 +1582,7 @@
createDirectMessage(screenName);
}
- void PopupClicked(Tweet tweet)
+ void PopupClicked(ITweet tweet)
{
if (this.WindowState == WindowState.Minimized)
{
@@ -1788,7 +1792,7 @@
// TODO: this should be displayed somewhere else instead of the main tweets listbox.
for (int i = searchResults.Count - 1; i >= 0; i--)
{
- Tweet tweet = searchResults[i];
+ ITweet tweet = searchResults[i];
if (!tweets.Contains(tweet))
{
tweets.Insert(0, tweet);
Index: Witty/Witty/App.xaml.cs
===================================================================
--- Witty/Witty/App.xaml.cs (revision 349)
+++ Witty/Witty/App.xaml.cs (working copy)
@@ -16,7 +16,7 @@
public static readonly ILog Logger = LogManager.GetLogger("Witty.Logging");
// Global variable for the user
- public static User LoggedInUser = null;
+ public static IUser LoggedInUser = null;
protected override void OnStartup(StartupEventArgs e)
{
Index: Witty/Witty/Popup.xaml.cs
===================================================================
--- Witty/Witty/Popup.xaml.cs (revision 349)
+++ Witty/Witty/Popup.xaml.cs (working copy)
@@ -11,14 +11,14 @@
public delegate void PopupReplyClickedDelegate(string screenName);
public delegate void PopupDirectMessageClickedDelegate(string screenName);
public delegate void PopupCloseButtonClickedDelegate(Popup p);
- public delegate void PopupClickedDelegate(Tweet tweet);
+ public delegate void PopupClickedDelegate(ITweet tweet);
///
/// Interaction logic for Popup.xaml
///
public partial class Popup : Window
{
- private Tweet _tweet;
+ private ITweet _tweet;
private Storyboard sbFadeOut;
private Storyboard ShowPopup;
private TimeSpan ts = new TimeSpan();
@@ -28,7 +28,7 @@
public event PopupCloseButtonClickedDelegate CloseButtonClicked;
public event PopupClickedDelegate Clicked;
- public Popup(Tweet tweet, Int32 numPopups) : this(tweet.User.ScreenName, tweet.Text, tweet.User.ImageUrl, numPopups)
+ public Popup(ITweet tweet, Int32 numPopups) : this(tweet.User.ScreenName, tweet.Text, tweet.User.ImageUrl, numPopups)
{
_tweet = tweet;
}
@@ -43,9 +43,10 @@
tweetText.Text = body;
userName.Text = heading;
- ImageSourceConverter conv = new ImageSourceConverter();
- avatarImage.Source = (ImageSource)conv.ConvertFromString(imageSource);
-
+ if (imageSource != null) {
+ ImageSourceConverter conv = new ImageSourceConverter();
+ avatarImage.Source = (ImageSource)conv.ConvertFromString(imageSource);
+ }
this.Topmost = true;
sbFadeOut = (Storyboard)FindResource("sbFadeOut");
Index: Witty/Witty/LoginControl.xaml.cs
===================================================================
--- Witty/Witty/LoginControl.xaml.cs (revision 349)
+++ Witty/Witty/LoginControl.xaml.cs (working copy)
@@ -2,6 +2,7 @@
using System.Windows;
using log4net;
using TwitterLib;
+using IdenticaLib;
namespace Witty
{
@@ -11,8 +12,8 @@
private readonly Properties.Settings AppSettings = Properties.Settings.Default;
- private delegate void LoginDelegate(TwitterNet arg);
- private delegate void PostLoginDelegate(User arg);
+ private delegate void LoginDelegate(IServiceApi arg);
+ private delegate void PostLoginDelegate(IUser arg);
public LoginControl()
{
@@ -23,7 +24,7 @@
{
// Jason Follas: Reworked Web Proxy - don't need to explicitly pass into TwitterNet ctor
//TwitterNet twitter = new TwitterNet(UsernameTextBox.Text, TwitterNet.ToSecureString(PasswordTextBox.Password), WebProxyHelper.GetConfiguredWebProxy());
- TwitterNet twitter = new TwitterNet(UsernameTextBox.Text, TwitterNet.ToSecureString(PasswordTextBox.Password)); //, WebProxyHelper.GetConfiguredWebProxy());
+ IServiceApi twitter = new IdenticaAPI(UsernameTextBox.Text, TwitterNet.ToSecureString(PasswordTextBox.Password)); //, WebProxyHelper.GetConfiguredWebProxy());
// Jason Follas: Twitter proxy servers, anyone? (Network Nazis who block twitter.com annoy me)
twitter.TwitterServerUrl = AppSettings.TwitterHost;
@@ -34,7 +35,7 @@
new LoginDelegate(TryLogin), twitter);
}
- private void TryLogin(TwitterNet twitter)
+ private void TryLogin(IServiceApi twitter)
{
try
{
@@ -60,7 +61,7 @@
}
}
- private void UpdatePostLoginInterface(User user)
+ private void UpdatePostLoginInterface(IUser user)
{
App.LoggedInUser = user;
if (App.LoggedInUser != null)
Index: Witty/WittyUnitTests/TwitterNetTest.cs
===================================================================
--- Witty/WittyUnitTests/TwitterNetTest.cs (revision 349)
+++ Witty/WittyUnitTests/TwitterNetTest.cs (working copy)
@@ -14,7 +14,7 @@
protected int alanUserId;
protected int alanFriendsCount;
- protected User testUser;
+ protected IUser testUser;
[SetUp]
public void Init()
@@ -31,7 +31,7 @@
public void Login()
{
TwitterNet twitter = new TwitterNet(username, password);
- User user = twitter.Login();
+ IUser user = twitter.Login();
Assert.AreEqual(userId, user.Id);
}
@@ -75,7 +75,7 @@
public void GetUser()
{
TwitterNet twitter = new TwitterNet(username,password);
- User user = twitter.GetUser(userId);
+ IUser user = twitter.GetUser(userId);
Assert.AreEqual(username, user.ScreenName);
}
}
Index: Witty/IdenticaLib/User.cs
===================================================================
--- Witty/IdenticaLib/User.cs (revision 0)
+++ Witty/IdenticaLib/User.cs (revision 0)
@@ -0,0 +1,240 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TwitterLib;
+using System.ComponentModel;
+
+namespace IdenticaLib {
+
+ [Serializable]
+ public class User : INotifyPropertyChanged, TwitterLib.IUser {
+ private int id;
+ private string name;
+ private string screenName;
+ private string imageUrl;
+ private string siteUrl;
+ private string location;
+ private string description;
+
+ public int Id {
+ get { return id; }
+ set {
+ if (value != id) {
+ id = value;
+ OnPropertyChanged("Id");
+ }
+ }
+ }
+
+ public string Name {
+ get { return name; }
+ set {
+ if (value != name) {
+ name = value;
+ OnPropertyChanged("Name");
+ }
+ }
+ }
+
+ public string ScreenName {
+ get { return screenName; }
+ set {
+ if (value != screenName) {
+ screenName = value;
+ OnPropertyChanged("ScreenName");
+ }
+ }
+ }
+
+ public string ImageUrl {
+ get {
+ return imageUrl;
+ }
+ set {
+ if (value != imageUrl) {
+ imageUrl = value;
+ OnPropertyChanged("ImageUrl");
+ }
+ }
+ }
+
+ public string SiteUrl {
+ get { return siteUrl; }
+ set {
+ if (value != siteUrl) {
+ siteUrl = value;
+ OnPropertyChanged("SiteUrl");
+ }
+ }
+ }
+
+ public string TwitterUrl {
+ get {
+ return "http://identica.ca/" + screenName;
+ }
+ }
+
+ public string Location {
+ get { return location; }
+ set {
+ if (value != location) {
+ location = value;
+ OnPropertyChanged("location");
+ }
+ }
+ }
+
+ public string Description {
+ get { return description; }
+ set {
+ if (value != description) {
+ description = value;
+ OnPropertyChanged("Description");
+ }
+ }
+ }
+
+ ///
+ /// The user name along with the screenname. This makes binding a little easier.
+ ///
+ public string FullName {
+ get { return Name + " (" + screenName + ")"; }
+ }
+
+ private string backgroundColor;
+
+ public string BackgroundColor {
+ get { return backgroundColor; }
+ set {
+ if (value != backgroundColor) {
+ backgroundColor = value;
+ OnPropertyChanged("BackgroundColor");
+ }
+ }
+ }
+
+ private string textColor;
+
+ public string TextColor {
+ get { return textColor; }
+ set {
+ if (value != textColor) {
+ textColor = value;
+ OnPropertyChanged("TextColor");
+ }
+ }
+ }
+ private string linkColor;
+
+ public string LinkColor {
+ get { return linkColor; }
+ set {
+ if (value != linkColor) {
+ linkColor = value;
+ OnPropertyChanged("LinkColor");
+ }
+ }
+ }
+
+ private string sidebarFillColor;
+
+ public string SidebarFillColor {
+ get { return sidebarFillColor; }
+ set {
+ if (value != sidebarFillColor) {
+ sidebarFillColor = value;
+ OnPropertyChanged("SidebarFillColor");
+ }
+ }
+ }
+
+ private string sidebarBorderColor;
+
+ public string SidebarBorderColor {
+ get { return sidebarBorderColor; }
+ set {
+ if (value != sidebarBorderColor) {
+ sidebarBorderColor = value;
+ OnPropertyChanged("SidebarBorderColor");
+ }
+ }
+ }
+
+ private ITweet tweet;
+
+ public ITweet Tweet {
+ get { return tweet; }
+ set {
+ if (value != tweet) {
+ tweet = value;
+ OnPropertyChanged("Tweet");
+ }
+ }
+ }
+
+ private int followingCount;
+
+ public int FollowingCount {
+ get { return followingCount; }
+ set {
+ if (value != followingCount) {
+ followingCount = value;
+ OnPropertyChanged("FollowingCount");
+ }
+ }
+ }
+
+ private int followersCount;
+
+ public int FollowersCount {
+ get { return followersCount; }
+ set {
+ if (value != followersCount) {
+ followersCount = value;
+ OnPropertyChanged("FollowersCount");
+ }
+ }
+ }
+
+ private int statusesCount;
+
+ public int StatusesCount {
+ get { return statusesCount; }
+ set {
+ if (value != statusesCount) {
+ statusesCount = value;
+ OnPropertyChanged("StatusesCount");
+ }
+ }
+ }
+
+ private int favoritesCount;
+
+ public int FavoritesCount {
+ get { return favoritesCount; }
+ set {
+ if (value != favoritesCount) {
+ favoritesCount = value;
+ OnPropertyChanged("FavoritesCount");
+ }
+ }
+ }
+
+ #region INotifyPropertyChanged Members
+
+ ///
+ /// INotifyPropertyChanged requires a property called PropertyChanged.
+ ///
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// Fires the event for the property when it changes.
+ ///
+ protected virtual void OnPropertyChanged(string propertyName) {
+ if (PropertyChanged != null)
+ PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ #endregion
+ }
+}
Index: Witty/IdenticaLib/Notice.cs
===================================================================
--- Witty/IdenticaLib/Notice.cs (revision 0)
+++ Witty/IdenticaLib/Notice.cs (revision 0)
@@ -0,0 +1,199 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TwitterLib;
+using System.ComponentModel;
+
+namespace IdenticaLib {
+ [Serializable]
+ public class Notice : INotifyPropertyChanged, TwitterLib.ITweet {
+ #region Private fields
+
+ private double id;
+ private DateTime? dateCreated;
+ private string relativeTime;
+ private string text;
+ private string source;
+ private IUser user;
+ private bool isNew;
+ private int index;
+ private bool isSearchResult;
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// The Tweet id
+ ///
+ public double Id {
+ get { return id; }
+ set {
+ if (value != id) {
+ id = value;
+ OnPropertyChanged("Id");
+ }
+ }
+ }
+
+ ///
+ /// Date and time the tweet was added
+ ///
+ public DateTime? DateCreated {
+ get { return dateCreated; }
+ set {
+ if (value != dateCreated) {
+ dateCreated = value;
+ OnPropertyChanged("DateCreated");
+ UpdateRelativeTime();
+ }
+ }
+ }
+
+ ///
+ /// The Tweet text
+ ///
+ public string Text {
+ get { return text; }
+ set {
+ if (value != text) {
+ text = value;
+ OnPropertyChanged("Text");
+ }
+ }
+ }
+
+ ///
+ /// The Tweet source
+ ///
+ public string Source {
+ get { return "from " + source; }
+ set {
+ if (value != source) {
+ source = value;
+ OnPropertyChanged("Source");
+ }
+ }
+ }
+
+ ///
+ /// Twitter User associated with the Tweet
+ ///
+ public IUser User {
+ get { return user; }
+ set {
+ if (value != user) {
+ user = value;
+ OnPropertyChanged("User");
+ }
+ }
+ }
+
+ ///
+ /// How long ago the tweet was added based on DatedCreated and DateTime.Now
+ ///
+ public string RelativeTime {
+ get {
+ return relativeTime;
+ }
+ set {
+ relativeTime = value;
+ OnPropertyChanged("Relativetime");
+ }
+ }
+
+ public bool IsNew {
+ get { return isNew; }
+ set {
+ if (value != isNew) {
+ isNew = value;
+ OnPropertyChanged("IsNew");
+ }
+ }
+ }
+
+ public int Index {
+ get { return index; }
+ set {
+ if (value != index) {
+ index = value;
+ OnPropertyChanged("Index");
+ }
+ }
+ }
+
+ public bool IsSearchResult {
+ get { return isSearchResult; }
+ set {
+ if (value != isSearchResult) {
+ isSearchResult = value;
+ OnPropertyChanged("IsSearchResult");
+ }
+ }
+ }
+ #endregion
+
+ #region INotifyPropertyChanged Members
+
+ ///
+ /// INotifyPropertyChanged requires a property called PropertyChanged.
+ ///
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ ///
+ /// Fires the event for the property when it changes.
+ ///
+ protected virtual void OnPropertyChanged(string propertyName) {
+ if (PropertyChanged != null)
+ PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+ #endregion
+
+ #region IEquatable Members
+
+ ///
+ /// Tweets are the same if they have the same Id.
+ /// Collection.Contains needs IEquatable implemented to be effective.
+ ///
+ public bool Equals(ITweet other) {
+ if (other == null)
+ throw new ArgumentNullException("other");
+
+ return (Id == other.Id);
+ }
+
+ #endregion
+
+ ///
+ /// Updates the relativeTime based on the DateCreated and DateTime.Now
+ ///
+ public void UpdateRelativeTime() {
+ if (!dateCreated.HasValue)
+ RelativeTime = string.Empty;
+
+ DateTime StatusCreatedDate = (DateTime)dateCreated;
+
+ TimeSpan ts = new TimeSpan(DateTime.Now.Ticks - StatusCreatedDate.Ticks);
+ double delta = ts.TotalSeconds;
+
+ if (delta <= 1) {
+ RelativeTime = "a second ago";
+ } else if (delta < 60) {
+ RelativeTime = ts.Seconds + " seconds ago";
+ } else if (delta < 120) {
+ RelativeTime = "about a minute ago";
+ } else if (delta < (45 * 60)) {
+ RelativeTime = ts.Minutes + " minutes ago";
+ } else if (delta < (90 * 60)) {
+ RelativeTime = "about an hour ago";
+ } else if (delta < (24 * 60 * 60)) {
+ RelativeTime = "about " + ts.Hours + " hours ago";
+ } else if (delta < (48 * 60 * 60)) {
+ RelativeTime = "1 day ago";
+ } else {
+ RelativeTime = ts.Days + " days ago";
+ }
+ }
+ }
+}
Index: Witty/IdenticaLib/IdenticaLib.csproj
===================================================================
--- Witty/IdenticaLib/IdenticaLib.csproj (revision 0)
+++ Witty/IdenticaLib/IdenticaLib.csproj (revision 0)
@@ -0,0 +1,69 @@
+
+
+
+ Debug
+ AnyCPU
+ 9.0.21022
+ 2.0
+ {580176B8-7199-406C-AFFB-C8B81450AEDC}
+ Library
+ Properties
+ IdenticaLib
+ IdenticaLib
+ v3.0
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+ 3.5
+
+
+ 3.5
+
+
+
+
+ 3.0
+
+
+
+
+
+
+
+
+
+
+ {6CB55919-6C11-43A1-8769-57461BE5B0AE}
+ TwitterLib
+
+
+
+
+
\ No newline at end of file
Index: Witty/IdenticaLib/Properties/AssemblyInfo.cs
===================================================================
--- Witty/IdenticaLib/Properties/AssemblyInfo.cs (revision 0)
+++ Witty/IdenticaLib/Properties/AssemblyInfo.cs (revision 0)
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("IdenticaLib")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("IdenticaLib")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2008")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("4646c9a5-75c1-4860-87af-9104ac6877b7")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
Index: Witty/IdenticaLib/IdenticaAPI.cs
===================================================================
--- Witty/IdenticaLib/IdenticaAPI.cs (revision 0)
+++ Witty/IdenticaLib/IdenticaAPI.cs (revision 0)
@@ -0,0 +1,1255 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using TwitterLib;
+using System.Xml;
+using System.IO;
+using System.Net;
+using System.Security;
+using System.Web;
+using System.Text.RegularExpressions;
+using System.Globalization;
+
+namespace IdenticaLib {
+ public class IdenticaAPI : IServiceApi
+ {
+ #region Private Fields
+ private string username;
+ private SecureString password;
+ private string loginUrl;
+ private string publicTimelineUrl;
+ private string friendsTimelineUrl;
+ private string userTimelineUrl;
+ private string repliesTimelineUrl;
+ private string directMessagesUrl;
+ private string updateUrl;
+ private string friendsUrl;
+ private string followersUrl;
+ private string userShowUrl;
+ private string sendMessageUrl;
+ private string destroyUrl;
+ private string destroyDirectMessageUrl;
+ private string createFriendshipUrl;
+ private string twitterServerUrl;
+ private string format;
+ private IWebProxy webProxy;
+ private CookieContainer cookieJar = new CookieContainer();
+
+ private IUser currentLoggedInUser;
+
+ private static int characterLimit;
+ private string clientName;
+
+ #endregion
+
+ #region Public Properties
+
+ public IUser CurrentlyLoggedInUser
+ {
+ get
+ {
+ if (null == currentLoggedInUser)
+ {
+ currentLoggedInUser = Login();
+ }
+ return currentLoggedInUser;
+ }
+ set
+ {
+ currentLoggedInUser = value;
+ }
+ }
+
+ ///
+ /// Identi.ca username
+ ///
+ public string UserName
+ {
+ get { return (null == currentLoggedInUser ? currentLoggedInUser.Name: String.Empty) ; }
+ }
+
+ ///
+ /// Identi.ca password
+ ///
+ public SecureString Password
+ {
+ get { return password; }
+ set { password = value; }
+ }
+
+ ///
+ /// Web proxy to use when communicating with the Identi.ca service
+ ///
+ public IWebProxy WebProxy
+ {
+ get { return webProxy; }
+ set { webProxy = value; }
+ }
+
+ public string LoginUrl {
+ get {
+ if (string.IsNullOrEmpty(loginUrl))
+ return "http://identi.ca/main/login";
+ else
+ return loginUrl;
+ }
+ set { loginUrl = value; }
+ }
+
+
+
+ ///
+ /// Url to the Identi.ca Public Timeline.
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string PublicTimelineUrl
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(publicTimelineUrl))
+ return "http://identi.ca/rss";
+ else
+ return publicTimelineUrl;
+ }
+ set { publicTimelineUrl = value; }
+ }
+
+ ///
+ /// Url to the Identi.ca Friends Timeline. Defaults to http://Identi.ca.com/statuses/friends_timeline
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string FriendsTimelineUrl
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(friendsTimelineUrl))
+ return "http://identi.ca/{username}/all/rss";
+ else
+ return friendsTimelineUrl;
+ }
+ set { friendsTimelineUrl = value; }
+ }
+
+ ///
+ /// Url to the user's timeline. Defaults to http://Identi.ca.com/statuses/user_timeline
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string UserTimelineUrl
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(userTimelineUrl))
+ return "http://identi.ca/{username}/rss";
+ else
+ return userTimelineUrl;
+ }
+ set { userTimelineUrl = value; }
+ }
+
+ ///
+ /// Url to the 20 most recent replies (status updates prefixed with @username posted by users who are friends with the user being replied to) to the authenticating user.
+ /// Defaults to http://Identi.ca.com/statuses/user_timeline
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string RepliesTimelineUrl
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(repliesTimelineUrl))
+ return "http://identi.ca/{username}/replies/rss";
+ else
+ return repliesTimelineUrl;
+ }
+ set { repliesTimelineUrl = value; }
+ }
+
+ ///
+ /// Url to the list of the 20 most recent direct messages sent to the authenticating user.
+ ///
+ ///
+ /// Identi.ca does not yet support direct messages
+ ///
+ public string DirectMessagesUrl
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ set { directMessagesUrl = value; }
+ }
+
+ ///
+ /// Url to the Identi.ca HTTP Post.
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string UpdateUrl
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(updateUrl))
+ return "http://identi.ca/notice/new";
+ else
+ return updateUrl;
+ }
+ set { updateUrl = value; }
+ }
+
+ ///
+ /// Url to the user's friends. Defaults to http://Identi.ca.com/statuses/friends
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string FriendsUrl
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(friendsUrl))
+ return "http://Identi.ca.com/statuses/friends";
+ else
+ return friendsUrl;
+ }
+ set { friendsUrl = value; }
+ }
+
+ ///
+ /// Url to the user's followers. Defaults to http://Identi.ca.com/statuses/followers
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string FollowersUrl
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(followersUrl))
+ return "http://Identi.ca.com/statuses/followers";
+ else
+ return followersUrl;
+ }
+ set { followersUrl = value; }
+ }
+
+ ///
+ /// Returns extended information of a given user, specified by ID or screen name as per the required id parameter below.
+ /// This information includes design settings, so third party developers can theme their widgets according to a given user's preferences.
+ /// Defaults to http://Identi.ca.com/users/show/
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string UserShowUrl
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(userShowUrl))
+ return "http://identi.ca/";
+ else
+ return userShowUrl;
+ }
+ set { userShowUrl = value; }
+ }
+
+ ///
+ /// Url to sends a new direct message to the specified user from the authenticating user. Defaults to http://Identi.ca.com/direct_messages/new
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string SendMessageUrl
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(sendMessageUrl))
+ return "http://identi.ca/notice/new";
+ else
+ return sendMessageUrl;
+ }
+ set { sendMessageUrl = value; }
+ }
+
+ ///
+ /// Url to destroy a status from the authenticating user. Defaults to http://Identi.ca.com/statuses/destroy
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string DestroyUrl
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(destroyUrl))
+ return "http://Identi.ca.com/statuses/destroy/";
+ else
+ return destroyUrl;
+ }
+ set { destroyUrl = value; }
+ }
+
+ ///
+ /// Url to destroy a direct message from the authenticating user. Defaults to http://Identi.ca.com/direct_messages/destroy/
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string DestroyDirectMessageUrl
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(destroyDirectMessageUrl))
+ return "http://Identi.ca.com/direct_messages/destroy/";
+ else
+ return destroyDirectMessageUrl;
+ }
+ set { destroyDirectMessageUrl = value; }
+ }
+
+
+
+ public string CreateFriendshipUrl
+ {
+ get {
+ if (string.IsNullOrEmpty(createFriendshipUrl))
+ {
+ return "http://Identi.ca.com/friendships/create/";
+ }
+ else
+ {
+ return createFriendshipUrl;
+ }
+ }
+ set { createFriendshipUrl = value; }
+ }
+
+
+
+ ///
+ /// URL of the Twitter host. Defaults to http://twitter.com/. Why would
+ /// you need to override this? Think: Alternate endpoints, like other
+ /// services with identical API's, or maybe a Twitter Proxy.
+ ///
+ public string TwitterServerUrl {
+ get {
+ if (String.IsNullOrEmpty(twitterServerUrl))
+ return "http://identi.ca/";
+ else
+ return twitterServerUrl;
+ }
+ set {
+ twitterServerUrl = value;
+
+ if (!twitterServerUrl.EndsWith("/"))
+ twitterServerUrl += "/";
+ }
+ }
+
+ ///
+ /// The format of the results from the Identi.ca API. Ex: .xml, .json, .rss, .atom. Defaults to ".xml"
+ ///
+ ///
+ /// This value should only be changed if Identi.ca API urls have been changed on http://laconi.ca/Main/Version0API
+ ///
+ public string Format
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(format))
+ return ".xml";
+ else
+ return format;
+ }
+ set { format = value; }
+ }
+
+ ///
+ /// The number of characters available for the Tweet text. Defaults to 140.
+ ///
+ ///
+ /// This value should only be changed if the character limit on Identi.ca.com has been changed.
+ ///
+ public static int CharacterLimit
+ {
+ get
+ {
+ if (characterLimit == 0)
+ return 140;
+ else
+ return characterLimit;
+ }
+ set { characterLimit = value; }
+ }
+
+ ///
+ /// The name of the current client. Defaults to "Witty"
+ ///
+ ///
+ /// This value can be changed if you're using this Library for your own Identi.ca client
+ ///
+ public string ClientName
+ {
+ get
+ {
+ if (string.IsNullOrEmpty(clientName))
+ return "witty";
+ else
+ return clientName;
+ }
+ set { clientName = value; }
+ }
+
+ #endregion
+
+ #region Constructors
+
+ ///
+ /// Unauthenticated constructor
+ ///
+ public IdenticaAPI()
+ {
+ this.Format = String.Empty;
+ }
+
+ ///
+ /// Authenticated constructor
+ ///
+ public IdenticaAPI(string username, SecureString password)
+ {
+ this.username = username;
+ this.password = password;
+ this.Format = String.Empty;
+ }
+
+ ///
+ /// Authenticated constructor with Proxy
+ ///
+ public IdenticaAPI(string username, SecureString password, IWebProxy webProxy)
+ {
+ this.username = username;
+ this.password = password;
+ this.webProxy = webProxy;
+ this.Format = String.Empty;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ #region Timeline Methods
+
+ ///
+ /// Retrieves the public timeline
+ ///
+ public TweetCollection GetPublicTimeline()
+ {
+ return RetrieveTimeline(Timeline.Public);
+ }
+
+ ///
+ /// Retrieves the public timeline. Narrows the result to after the since date.
+ ///
+ public TweetCollection GetPublicTimeline(string since)
+ {
+ return RetrieveTimeline(Timeline.Public, since);
+ }
+
+ ///
+ /// Retrieves the friends timeline
+ ///
+ public TweetCollection GetFriendsTimeline()
+ {
+ if (!string.IsNullOrEmpty(username))
+ return RetrieveTimeline(Timeline.Friends);
+ else
+ return RetrieveTimeline(Timeline.Public);
+ }
+
+ ///
+ /// Retrieves the friends timeline. Narrows the result to after the since date.
+ ///
+ public TweetCollection GetFriendsTimeline(string since)
+ {
+ if (!string.IsNullOrEmpty(username))
+ return RetrieveTimeline(Timeline.Friends, since);
+ else
+ return RetrieveTimeline(Timeline.Public, since);
+ }
+
+ ///
+ /// Retrieves the friends timeline. Narrows the result to after the since date.
+ ///
+ public TweetCollection GetFriendsTimeline(string since, string userId)
+ {
+ return RetrieveTimeline(Timeline.Friends, since, userId);
+ }
+
+ public TweetCollection GetUserTimeline(string userId)
+ {
+ return RetrieveTimeline(Timeline.User, "", userId);
+ }
+
+ public TweetCollection GetReplies()
+ {
+ return RetrieveTimeline(Timeline.Replies);
+ }
+
+ #endregion
+
+ ///
+ /// Returns the authenticated user's friends who have most recently updated, each with current status inline.
+ ///
+ public UserCollection GetFriends()
+ {
+ return GetFriends(CurrentlyLoggedInUser.Id);
+ }
+
+ ///
+ /// Returns the user's friends who have most recently updated, each with current status inline.
+ ///
+ public UserCollection GetFriends(int userId)
+ {
+ UserCollection users = new UserCollection();
+
+ // Identi.ca expects http://Identi.ca.com/statuses/friends/12345.xml
+ string requestURL = FriendsUrl + "/" + userId + Format;
+
+ int friendsCount = 0;
+
+ // Since the API docs state "Returns up to 100 of the authenticating user's friends", we need
+ // to use the page param and to fetch ALL of the users friends. We can find out how many pages
+ // we need by dividing the # of friends by 100 and rounding any remainder up.
+ // merging the responses from each request may be tricky.
+ if (currentLoggedInUser != null && currentLoggedInUser.Id == userId)
+ {
+ friendsCount = CurrentlyLoggedInUser.FollowingCount;
+ }
+ else
+ {
+ // need to make an extra call to Identi.ca
+ IUser user = GetUser(userId);
+ friendsCount = user.FollowingCount;
+ }
+
+ int numberOfPagesToFetch = (friendsCount / 100) + 1;
+
+ string pageRequestUrl = requestURL;
+
+ for (int count = 1; count <= numberOfPagesToFetch; count++)
+ {
+ pageRequestUrl = requestURL + "?page=" + count;
+
+ // Create the web request
+ HttpWebRequest request = WebRequest.Create(pageRequestUrl) as HttpWebRequest;
+
+ // Add credendtials to request
+ request.Credentials = new NetworkCredential(username, TwitterNet.ToInsecureString(password));
+
+ try
+ {
+ // Get the Response
+ using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
+ {
+ // Get the response stream
+ StreamReader reader = new StreamReader(response.GetResponseStream());
+
+ // Create a new XmlDocument
+ XmlDocument doc = new XmlDocument();
+
+ // Load data
+ doc.Load(reader);
+
+ // Get statuses with XPath
+ XmlNodeList nodes = doc.SelectNodes("/users/user");
+
+ foreach (XmlNode node in nodes)
+ {
+ IUser user = new User();
+ user.Id = int.Parse(node.SelectSingleNode("id").InnerText);
+ user.Name = node.SelectSingleNode("name").InnerText;
+ user.ScreenName = node.SelectSingleNode("screen_name").InnerText;
+ user.ImageUrl = node.SelectSingleNode("profile_image_url").InnerText;
+ user.SiteUrl = node.SelectSingleNode("url").InnerText;
+ user.Location = node.SelectSingleNode("location").InnerText;
+ user.Description = node.SelectSingleNode("description").InnerText;
+
+ users.Add(user);
+ }
+
+ }
+ }
+ catch (WebException webExcp)
+ {
+ // Get the WebException status code.
+ WebExceptionStatus status = webExcp.Status;
+ // If status is WebExceptionStatus.ProtocolError,
+ // there has been a protocol error and a WebResponse
+ // should exist. Display the protocol error.
+ if (status == WebExceptionStatus.ProtocolError)
+ {
+ // Get HttpWebResponse so that you can check the HTTP status code.
+ HttpWebResponse httpResponse = (HttpWebResponse)webExcp.Response;
+
+ switch ((int)httpResponse.StatusCode)
+ {
+ case 304: // 304 Not modified = no new tweets so ignore error.
+ break;
+ case 400: // rate limit exceeded
+ throw new RateLimitException("Rate limit exceeded. Clients may not make more than 70 requests per hour. Please try again in a few minutes.");
+ case 401: // unauthorized
+ throw new SecurityException("Not Authorized.");
+ default:
+ throw;
+ }
+ }
+ }
+ }
+ return users;
+ }
+
+ public IUser GetUser(int userId)
+ {
+ IUser user = new User();
+
+ // Identi.ca expects http://Identi.ca.com/users/show/12345.xml
+ string requestURL = UserShowUrl + userId + Format;
+
+ // Create the web request
+ HttpWebRequest request = WebRequest.Create(requestURL) as HttpWebRequest;
+
+ // Add credendtials to request
+ request.Credentials = new NetworkCredential(username, TwitterNet.ToInsecureString(password));
+
+ // Add configured web proxy
+ request.Proxy = webProxy;
+
+ using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
+ {
+ // Get the response stream
+ StreamReader reader = new StreamReader(response.GetResponseStream());
+
+ // Load the response data into a XmlDocument
+ XmlDocument doc = new XmlDocument();
+ doc.Load(reader);
+
+ // Get statuses with XPath
+ XmlNode userNode = doc.SelectSingleNode("user");
+
+ if (userNode != null)
+ {
+ user.Id = int.Parse(userNode.SelectSingleNode("id").InnerText);
+ user.Name = userNode.SelectSingleNode("name").InnerText;
+ user.ScreenName = userNode.SelectSingleNode("screen_name").InnerText;
+ user.ImageUrl = userNode.SelectSingleNode("profile_image_url").InnerText;
+ user.SiteUrl = userNode.SelectSingleNode("url").InnerText;
+ user.Location = userNode.SelectSingleNode("location").InnerText;
+ user.Description = userNode.SelectSingleNode("description").InnerText;
+ user.BackgroundColor = userNode.SelectSingleNode("profile_background_color").InnerText;
+ user.TextColor = userNode.SelectSingleNode("profile_text_color").InnerText;
+ user.LinkColor = userNode.SelectSingleNode("profile_link_color").InnerText;
+ user.SidebarBorderColor = userNode.SelectSingleNode("profile_sidebar_border_color").InnerText;
+ user.SidebarFillColor = userNode.SelectSingleNode("profile_sidebar_fill_color").InnerText;
+ user.FollowingCount = int.Parse(userNode.SelectSingleNode("friends_count").InnerText);
+ user.FavoritesCount = int.Parse(userNode.SelectSingleNode("favourites_count").InnerText);
+ user.StatusesCount = int.Parse(userNode.SelectSingleNode("statuses_count").InnerText);
+ user.FollowersCount = int.Parse(userNode.SelectSingleNode("followers_count").InnerText);
+ }
+ }
+
+ return user;
+ }
+
+ ///
+ /// Delete a tweet from a users timeline
+ ///
+ /// id of the Tweet to delete
+ public void DestroyTweet(double id)
+ {
+ string urlToCall = DestroyUrl + id + Format;
+ MakeDestroyRequestCall(urlToCall);
+ }
+
+ ///
+ /// Destroy a direct message sent by a user
+ ///
+ /// id of the direct message to delete
+ public void DestroyDirectMessage(double id)
+ {
+ string urlToCall = DestroyDirectMessageUrl + id + Format;
+ MakeDestroyRequestCall(urlToCall);
+ }
+
+ ///
+ /// Post new tweet to Identi.ca
+ ///
+ /// newly added tweet
+ public ITweet AddTweet(string text)
+ {
+ bool isDirectMessage = (text.StartsWith("d ", StringComparison.CurrentCultureIgnoreCase));
+
+ if (string.IsNullOrEmpty(text))
+ return null;
+
+ text = HttpUtility.UrlEncode(text);
+
+ // Create the web request
+ HttpWebRequest request = WebRequest.Create(UpdateUrl) as HttpWebRequest;
+
+ // Add authentication to request
+ request.Credentials = new NetworkCredential(username, TwitterNet.ToInsecureString(password));
+
+ // Add configured web proxy
+ request.Proxy = webProxy;
+ request.CookieContainer = cookieJar;
+
+ request.Method = "POST";
+
+ // Set values for the request back
+ request.ContentType = "application/x-www-form-urlencoded";
+ string param = "status_textarea=" + text;
+ request.ContentLength = param.Length;
+
+ // Write the request paramater
+ StreamWriter stOut = new StreamWriter(request.GetRequestStream(), System.Text.Encoding.ASCII);
+ stOut.Write(param);
+ stOut.Close();
+
+ ITweet tweet;
+
+ // Do the request to get the response
+ using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
+ {
+ // Get the response stream
+ StreamReader reader = new StreamReader(response.GetResponseStream());
+
+ // Load the response data into a XmlDocument
+ XmlDocument doc = new XmlDocument();
+ doc.Load(reader);
+ XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
+ mgr.AddNamespace("xhtml", "http://www.w3.org/1999/xhtml");
+ tweet = new Notice(); // Hack until I know what laconica returns from the post
+ tweet.Id = int.Parse(doc.SelectSingleNode("//xhtml:li[@class='notice_single']/@id",mgr).InnerText.Replace("notice-", ""));
+ tweet.Text = HttpUtility.UrlDecode(text);
+ tweet.User = CurrentlyLoggedInUser;
+ tweet.DateCreated = DateTime.Now;
+ tweet.IsNew = true;
+
+ //XmlNode node = doc.SelectSingleNode("status");
+
+ //tweet = new Notice();
+ //tweet.Id = double.Parse(node.SelectSingleNode("id").InnerText);
+ ////Defect 43 - Identi.ca incorrectly returns last tweet sent when you direct message someone.
+ //if (isDirectMessage)
+ // tweet.Text = HttpUtility.UrlDecode(text);
+ //else
+ // tweet.Text = HttpUtility.HtmlDecode(node.SelectSingleNode("text").InnerText);
+
+ //string source = HttpUtility.HtmlDecode(node.SelectSingleNode("source").InnerText);
+ //// Remove html from the source string
+ //if (!string.IsNullOrEmpty(source))
+ // tweet.Source = Regex.Replace(source, @"<(.|\n)*?>", string.Empty);
+
+ //string dateString = node.SelectSingleNode("created_at").InnerText;
+ //if (!string.IsNullOrEmpty(dateString))
+ //{
+ // tweet.DateCreated = DateTime.ParseExact(
+ // dateString,
+ // Identi.caCreatedAtDateFormat,
+ // CultureInfo.GetCultureInfoByIetfLanguageTag("en-us"), DateTimeStyles.AllowWhiteSpaces);
+ //}
+ //tweet.IsNew = true;
+
+ //User user = new User();
+ //XmlNode userNode = node.SelectSingleNode("user");
+ //user.Name = userNode.SelectSingleNode("name").InnerText;
+ //user.ScreenName = userNode.SelectSingleNode("screen_name").InnerText;
+ //user.ImageUrl = userNode.SelectSingleNode("profile_image_url").InnerText;
+ //user.SiteUrl = userNode.SelectSingleNode("url").InnerText;
+ //user.Location = userNode.SelectSingleNode("location").InnerText;
+ //user.Description = userNode.SelectSingleNode("description").InnerText;
+ //tweet.User = user;
+ }
+
+ return tweet;
+ }
+
+ ///
+ /// Authenticating with the provided credentials and retrieve the user's settings
+ ///
+ ///
+ public IUser Login()
+ {
+
+
+ IUser user = new User();
+
+ // Create the web request
+ HttpWebRequest request = WebRequest.Create(LoginUrl) as HttpWebRequest;
+ request.CookieContainer = cookieJar;
+ request.Method = "POST";
+ request.AllowAutoRedirect = false;
+ // Add credendtials to request
+ request.Credentials = new NetworkCredential(username, TwitterNet.ToInsecureString(password));
+
+ // Add configured web proxy
+ request.Proxy = webProxy;
+
+ try
+ {
+
+ // Set values for the request back
+ request.ContentType = "application/x-www-form-urlencoded";
+ string userParam = "nickname=" + username;
+ string passParam = "&password=" + TwitterNet.ToInsecureString(password);
+
+ request.ContentLength = userParam.Length + passParam.Length;
+
+ // Write the request paramater
+ StreamWriter stOut = new StreamWriter(request.GetRequestStream(), System.Text.Encoding.ASCII);
+ stOut.Write(userParam);
+ stOut.Write(passParam);
+ stOut.Close();
+
+
+ using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
+ {
+ // Get the response stream
+ StreamReader reader = new StreamReader(response.GetResponseStream());
+
+ // Load the response data into a XmlDocument
+ XmlDocument doc = new XmlDocument();
+ doc.Load(reader);
+
+ // Get statuses with XPath
+ XmlNode userNode = doc.SelectSingleNode("user");
+
+ if (userNode != null)
+ {
+ user.Id = int.Parse(userNode.SelectSingleNode("id").InnerText);
+ user.Name = userNode.SelectSingleNode("name").InnerText;
+ user.ScreenName = userNode.SelectSingleNode("screen_name").InnerText;
+ user.ImageUrl = userNode.SelectSingleNode("profile_image_url").InnerText;
+ user.SiteUrl = userNode.SelectSingleNode("url").InnerText;
+ user.Location = userNode.SelectSingleNode("location").InnerText;
+ user.Description = userNode.SelectSingleNode("description").InnerText;
+ //user.BackgroundColor = userNode.SelectSingleNode("profile_background_color").InnerText;
+ //user.TextColor = userNode.SelectSingleNode("profile_text_color").InnerText;
+ //user.LinkColor = userNode.SelectSingleNode("profile_link_color").InnerText;
+ //user.SidebarBorderColor = userNode.SelectSingleNode("profile_sidebar_border_color").InnerText;
+ //user.SidebarFillColor = userNode.SelectSingleNode("profile_sidebar_fill_color").InnerText;
+ //user.FollowingCount = int.Parse(userNode.SelectSingleNode("friends_count").InnerText);
+ //user.FavoritesCount = int.Parse(userNode.SelectSingleNode("favourites_count").InnerText);
+ //user.StatusesCount = int.Parse(userNode.SelectSingleNode("statuses_count").InnerText);
+ user.FollowersCount = int.Parse(userNode.SelectSingleNode("followers_count").InnerText);
+ }
+ }
+ }
+ catch (WebException webExcp)
+ {
+ // Get the WebException status code.
+ WebExceptionStatus status = webExcp.Status;
+ // If status is WebExceptionStatus.ProtocolError,
+ // there has been a protocol error and a WebResponse
+ // should exist. Display the protocol error.
+ if (status == WebExceptionStatus.ProtocolError)
+ {
+ // Get HttpWebResponse so that you can check the HTTP status code.
+ HttpWebResponse httpResponse = (HttpWebResponse)webExcp.Response;
+
+ switch ((int)httpResponse.StatusCode)
+ {
+ case 400: // rate limit exceeded
+ throw new RateLimitException("Rate limit exceeded. Clients may not make more than 70 requests per hour. Please try again in a few minutes.");
+ case 401: // unauthorized
+ return null;
+ case 407: // proxy authentication required
+ throw new ProxyAuthenticationRequiredException("Proxy authentication required.");
+ case 502 : //Bad Gateway, Identi.ca is freaking out.
+ throw new BadGatewayException("There was a problem calling the Identi.ca API");
+ default:
+ throw;
+ }
+ }
+ else
+ throw;
+ }
+ currentLoggedInUser = user;
+ return user;
+ }
+
+ static byte[] entropy = System.Text.Encoding.Unicode.GetBytes("WittyPasswordSalt");
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string EncryptString(System.Security.SecureString input)
+ {
+ byte[] encryptedData = System.Security.Cryptography.ProtectedData.Protect(
+ System.Text.Encoding.Unicode.GetBytes(ToInsecureString(input)),
+ entropy,
+ System.Security.Cryptography.DataProtectionScope.CurrentUser);
+ return Convert.ToBase64String(encryptedData);
+ }
+
+ public static SecureString ToSecureString(string input)
+ {
+ SecureString secure = new SecureString();
+ foreach (char c in input)
+ {
+ secure.AppendChar(c);
+ }
+ secure.MakeReadOnly();
+ return secure;
+ }
+
+ public static string ToInsecureString(SecureString input)
+ {
+ string returnValue = string.Empty;
+ IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input);
+ try
+ {
+ returnValue = System.Runtime.InteropServices.Marshal.PtrToStringBSTR(ptr);
+ }
+ finally
+ {
+ System.Runtime.InteropServices.Marshal.ZeroFreeBSTR(ptr);
+ }
+ return returnValue;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static SecureString DecryptString(string encryptedData)
+ {
+ try
+ {
+ byte[] decryptedData = System.Security.Cryptography.ProtectedData.Unprotect(
+ Convert.FromBase64String(encryptedData),
+ entropy,
+ System.Security.Cryptography.DataProtectionScope.CurrentUser);
+ return ToSecureString(System.Text.Encoding.Unicode.GetString(decryptedData));
+ }
+ catch
+ {
+ return new SecureString();
+ }
+ }
+
+ ///
+ /// Gets direct messages for the user
+ ///
+ /// Collection of direct messages
+ public DirectMessageCollection RetrieveMessages()
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SendMessage(string user, string text)
+ {
+ if (string.IsNullOrEmpty(text))
+ return;
+
+ text = HttpUtility.UrlEncode(text);
+
+ // Create the web request
+ HttpWebRequest request = WebRequest.Create(SendMessageUrl + Format) as HttpWebRequest;
+
+ // Add authentication to request
+ request.Credentials = new NetworkCredential(username, TwitterNet.ToInsecureString(password));
+
+ // Add configured web proxy
+ request.Proxy = webProxy;
+ request.CookieContainer = cookieJar;
+
+ request.Method = "POST";
+
+ // Set values for the request back
+ request.ContentType = "application/x-www-form-urlencoded";
+ string postbody = "&status_textarea="+text;
+
+ request.ContentLength = postbody.Length;
+
+ // Write the request paramater
+ StreamWriter stOut = new StreamWriter(request.GetRequestStream(), System.Text.Encoding.ASCII);
+ stOut.Write(postbody);
+ stOut.Close();
+
+ try
+ {
+ // Perform the web request
+ request.GetResponse();
+ }
+ catch (WebException webExcp)
+ {
+ // Get the WebException status code.
+ WebExceptionStatus status = webExcp.Status;
+ // If status is WebExceptionStatus.ProtocolError,
+ // there has been a protocol error and a WebResponse
+ // should exist. Display the protocol error.
+ if (status == WebExceptionStatus.ProtocolError)
+ {
+ // Get HttpWebResponse so that you can check the HTTP status code.
+ HttpWebResponse httpResponse = (HttpWebResponse)webExcp.Response;
+
+ switch ((int)httpResponse.StatusCode)
+ {
+ case 401: // unauthorized
+ throw new SecurityException("Not Authorized.");
+ default:
+ throw;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Follow the user specified by userId
+ ///
+ ///
+ public void FollowUser(string userName)
+ {
+ string followUrl = CreateFriendshipUrl + userName + Format;
+ MakeTwitterApiCall(followUrl);
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ ///
+ /// Retrieves the specified timeline from Identi.ca
+ ///
+ /// Collection of Tweets
+ private TweetCollection RetrieveTimeline(Timeline timeline)
+ {
+ return RetrieveTimeline(timeline, string.Empty);
+ }
+
+ ///
+ /// Retrieves the specified timeline from Identi.ca
+ ///
+ /// Collection of Tweets
+ private TweetCollection RetrieveTimeline(Timeline timeline, string since)
+ {
+ return RetrieveTimeline(timeline, since, string.Empty);
+ }
+
+ ///
+ /// The Main function for interfacing with the Identi.ca API
+ ///
+ /// Collection of Tweets. Identi.ca limits the max to 20.
+ private TweetCollection RetrieveTimeline(Timeline timeline, string since, string userId)
+ {
+ TweetCollection tweets = new TweetCollection();
+
+ string timelineUrl;
+
+ switch (timeline)
+ {
+ case Timeline.Public:
+ timelineUrl = PublicTimelineUrl;
+ break;
+ case Timeline.Friends:
+ timelineUrl = FriendsTimelineUrl;
+ break;
+ case Timeline.User:
+ timelineUrl = UserTimelineUrl;
+
+ break;
+ case Timeline.Replies:
+ timelineUrl = RepliesTimelineUrl;
+ break;
+ default:
+ timelineUrl = PublicTimelineUrl;
+ break;
+ }
+
+ //if (!string.IsNullOrEmpty(userId))
+ // timelineUrl += "/" + userId;
+ if (String.IsNullOrEmpty(userId)) {
+ userId = username;
+ }
+ timelineUrl = timelineUrl.Replace("{username}", userId);
+
+
+ if (!string.IsNullOrEmpty(since))
+ {
+ throw new NotImplementedException("Identi.ca does not implement the since parameter");
+ }
+
+ // Create the web request
+ HttpWebRequest request = WebRequest.Create(timelineUrl) as HttpWebRequest;
+
+ // Add configured web proxy
+ request.Proxy = webProxy;
+ request.CookieContainer = cookieJar;
+
+ // Friends and Replies timeline requests need to be authenticated
+ if (timeline == Timeline.Friends || timeline == Timeline.Replies)
+ {
+ // Add credendtials to request
+ request.Credentials = new NetworkCredential(username, TwitterNet.ToInsecureString(password));
+ }
+
+ // moved this out of the try catch to use it later on in the XMLException
+ // trying to fix a bug someone report
+ XmlDocument doc = new XmlDocument();
+ try
+ {
+ // Get the Web Response
+ using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
+ {
+ // Get the response stream
+ StreamReader reader = new StreamReader(response.GetResponseStream());
+
+ // Load the response data into a XmlDocument
+ doc.Load(reader);
+ XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
+ string rdfns = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+ mgr.AddNamespace("rdf", rdfns);
+ mgr.AddNamespace("dc", "http://purl.org/dc/elements/1.1/");
+ mgr.AddNamespace("rss", "http://purl.org/rss/1.0/");
+
+ // Get statuses with XPath
+ XmlNodeList nodes = doc.SelectNodes("//rss:item",mgr);
+
+ foreach (XmlNode node in nodes)
+ {
+ ITweet tweet = new Notice();
+ string about = node.Attributes["about", rdfns].Value;
+ tweet.Id = double.Parse(about.Replace("http://identi.ca/notice/", string.Empty));
+ tweet.Text = HttpUtility.HtmlDecode(node.SelectSingleNode("rss:title",mgr).InnerText);
+ string source = string.Empty;
+// if (!string.IsNullOrEmpty(source))
+ // tweet.Source = Regex.Replace(source, @"<(.|\n)*?>", string.Empty);
+
+ string dateString = node.SelectSingleNode("dc:date",mgr).InnerText;
+ if (!string.IsNullOrEmpty(dateString))
+ {
+ tweet.DateCreated = XmlConvert.ToDateTime(dateString);
+ }
+
+ User user = new User();
+ //XmlNode userNode = node.SelectSingleNode("user");
+ string description = node.SelectSingleNode("rss:description", mgr).InnerText;
+
+ user.Name = node.SelectSingleNode("dc:creator", mgr).InnerText;
+ user.ScreenName = description.Substring(0, description.IndexOf("'"));
+ user.ImageUrl = "http://identi.ca/" + user.ScreenName + "/avatar/48";
+ //user.SiteUrl = userNode.SelectSingleNode("url").InnerText;
+ //user.Location = userNode.SelectSingleNode("location").InnerText;
+ //user.Description = userNode.SelectSingleNode("description").InnerText;
+
+ tweet.User = user;
+
+ tweets.Add(tweet);
+ }
+
+ tweets.SaveToDisk();
+ }
+ }
+ catch (XmlException exXML)
+ {
+ // adding the XML document data to the exception so it will get logged
+ // so we can debug the issue
+ exXML.Data.Add("XMLDoc", doc);
+ throw;
+ }
+ catch (WebException webExcp)
+ {
+ // Get the WebException status code.
+ WebExceptionStatus status = webExcp.Status;
+ // If status is WebExceptionStatus.ProtocolError,
+ // there has been a protocol error and a WebResponse
+ // should exist. Display the protocol error.
+ if (status == WebExceptionStatus.ProtocolError)
+ {
+ // Get HttpWebResponse so that you can check the HTTP status code.
+ HttpWebResponse httpResponse = (HttpWebResponse)webExcp.Response;
+
+ switch ((int)httpResponse.StatusCode)
+ {
+ case 304: // 304 Not modified = no new tweets so ignore error.
+ break;
+ case 400: // rate limit exceeded
+ throw new RateLimitException("Rate limit exceeded. Clients may not make more than 70 requests per hour. Please try again in a few minutes.");
+ default:
+ throw;
+ }
+ }
+ }
+ return tweets;
+ }
+
+ ///
+ /// Generic call to destroy a status ** still in progress **
+ ///
+ ///
+ private void MakeDestroyRequestCall(string urlToCall)
+ {
+ MakeTwitterApiCall(urlToCall);
+ }
+
+ ///
+ /// Generic Identi.ca API call. Use this when you don't need/want to parse the return message
+ /// and just want to make a succeed/fail call to the API.
+ ///
+ ///
+ private void MakeTwitterApiCall(string urlToCall)
+ {
+ //REMARK: We may want to refactor this to return the message returned by the API.
+ // Create the web request
+ HttpWebRequest request = WebRequest.Create(urlToCall) as HttpWebRequest;
+
+ // Add authentication to request
+ request.Credentials = new NetworkCredential(username, TwitterNet.ToInsecureString(password));
+
+ // Add configured web proxy
+ request.Proxy = webProxy;
+
+ //request.Method = "GET";
+
+ try
+ {
+ // perform the destroy web request
+ request.GetResponse();
+ }
+ catch (WebException webExcp)
+ {
+ // Get the WebException status code.
+ WebExceptionStatus status = webExcp.Status;
+ // If status is WebExceptionStatus.ProtocolError,
+ // there has been a protocol error and a WebResponse
+ // should exist. Display the protocol error.
+ if (status == WebExceptionStatus.ProtocolError)
+ {
+ // Get HttpWebResponse so that you can check the HTTP status code.
+ HttpWebResponse httpResponse = (HttpWebResponse)webExcp.Response;
+
+ switch ((int)httpResponse.StatusCode)
+ {
+ case 401: // unauthorized
+ throw new SecurityException("Not Authorized.", webExcp);
+ default:
+ throw;
+ }
+ }
+ }
+ }
+
+ #endregion
+ }
+}
Index: Witty/TwitterLib/User.cs
===================================================================
--- Witty/TwitterLib/User.cs (revision 349)
+++ Witty/TwitterLib/User.cs (working copy)
@@ -202,9 +202,9 @@
}
}
- private Tweet tweet;
+ private ITweet tweet;
- public Tweet Tweet
+ public ITweet Tweet
{
get { return tweet; }
set
@@ -297,7 +297,7 @@
}
[Serializable]
- public class UserCollection : ObservableCollection
+ public class UserCollection : ObservableCollection
{
}
}
Index: Witty/TwitterLib/DirectMessage.cs
===================================================================
--- Witty/TwitterLib/DirectMessage.cs (revision 349)
+++ Witty/TwitterLib/DirectMessage.cs (working copy)
@@ -55,9 +55,9 @@
}
}
- private User sender;
+ private IUser sender;
- public User Sender
+ public IUser Sender
{
get { return sender; }
set
@@ -70,9 +70,9 @@
}
}
- private User recipient;
+ private IUser recipient;
- public User Recipient
+ public IUser Recipient
{
get { return recipient; }
set
@@ -192,9 +192,9 @@
#endregion
- public Tweet ToTweet()
+ public ITweet ToTweet()
{
- Tweet tweet = new Tweet();
+ ITweet tweet = new Tweet();
tweet.DateCreated = this.DateCreated;
tweet.User = this.Sender;
tweet.Text = this.Text;
Index: Witty/TwitterLib/ITweet.cs
===================================================================
--- Witty/TwitterLib/ITweet.cs (revision 349)
+++ Witty/TwitterLib/ITweet.cs (working copy)
@@ -1,10 +1,10 @@
using System;
namespace TwitterLib
{
- interface ITweet
+ public interface ITweet : IEquatable
{
DateTime? DateCreated { get; set; }
- bool Equals(Tweet other);
+ bool Equals(ITweet other);
double Id { get; set; }
int Index { get; set; }
bool IsNew { get; set; }
@@ -14,6 +14,6 @@
string Source { get; set; }
string Text { get; set; }
void UpdateRelativeTime();
- User User { get; set; }
+ IUser User { get; set; }
}
}
Index: Witty/TwitterLib/ITwitterNet.cs
===================================================================
--- Witty/TwitterLib/ITwitterNet.cs (revision 349)
+++ Witty/TwitterLib/ITwitterNet.cs (working copy)
@@ -2,7 +2,7 @@
{
public interface IServiceApi
{
- Tweet AddTweet(string text);
+ ITweet AddTweet(string text);
string ClientName { get; set; }
string CreateFriendshipUrl { get; set; }
@@ -28,9 +28,9 @@
UserCollection GetFriends(int userId);
UserCollection GetFriends();
- User CurrentlyLoggedInUser { get; set; }
- User GetUser(int userId);
- User Login();
+ IUser CurrentlyLoggedInUser { get; set; }
+ IUser GetUser(int userId);
+ IUser Login();
TweetCollection GetFriendsTimeline();
TweetCollection GetFriendsTimeline(string since, string userId);
Index: Witty/TwitterLib/IUser.cs
===================================================================
--- Witty/TwitterLib/IUser.cs (revision 349)
+++ Witty/TwitterLib/IUser.cs (working copy)
@@ -1,6 +1,6 @@
namespace TwitterLib
{
- interface IUser
+ public interface IUser
{
string BackgroundColor { get; set; }
string Description { get; set; }
@@ -20,7 +20,7 @@
string SiteUrl { get; set; }
int StatusesCount { get; set; }
string TextColor { get; set; }
- Tweet Tweet { get; set; }
+ ITweet Tweet { get; set; }
string TwitterUrl { get; }
}
}
Index: Witty/TwitterLib/Tweet.cs
===================================================================
--- Witty/TwitterLib/Tweet.cs (revision 349)
+++ Witty/TwitterLib/Tweet.cs (working copy)
@@ -11,7 +11,7 @@
/// Represents the status post for a Twitter User.
///
[Serializable]
- public class Tweet : INotifyPropertyChanged, IEquatable, TwitterLib.ITweet
+ public class Tweet : INotifyPropertyChanged, TwitterLib.ITweet
{
#region Private fields
@@ -20,7 +20,7 @@
private string relativeTime;
private string text;
private string source;
- private User user;
+ private IUser user;
private bool isNew;
private int index;
private bool isSearchResult;
@@ -97,7 +97,7 @@
///
/// Twitter User associated with the Tweet
///
- public User User
+ public IUser User
{
get { return user; }
set
@@ -190,15 +190,15 @@
/// Tweets are the same if they have the same Id.
/// Collection.Contains needs IEquatable implemented to be effective.
///
- public bool Equals(Tweet other)
+ public bool Equals(ITweet other)
{
if (other == null)
throw new ArgumentNullException("other");
- if (id != other.id)
+ if (id != other.Id)
return false;
if (Id == -1) // special type of tweet, so compare the user and text
- return ((user.Id == other.user.Id) && (text == other.text));
+ return ((user.Id == other.User.Id) && (text == other.Text));
return true;
}
@@ -257,7 +257,7 @@
/// Collection of Tweets
///
[Serializable]
- public class TweetCollection : ObservableCollection
+ public class TweetCollection : ObservableCollection
{
#region Class Constants
Index: Witty/TwitterLib/TwitterNet.cs
===================================================================
--- Witty/TwitterLib/TwitterNet.cs (revision 349)
+++ Witty/TwitterLib/TwitterNet.cs (working copy)
@@ -34,7 +34,7 @@
private IWebProxy webProxy = HttpWebRequest.DefaultWebProxy; // Jason Follas: Added initialization
private string twitterServerUrl; // Jason Follas
- private User currentLoggedInUser;
+ private IUser currentLoggedInUser;
// REMARK: might need to fix this for globalization
private readonly string twitterCreatedAtDateFormat = "ddd MMM dd HH:mm:ss zzzz yyyy"; // Thu Apr 26 01:36:08 +0000 2007
@@ -47,7 +47,7 @@
#region Public Properties
- public User CurrentlyLoggedInUser
+ public IUser CurrentlyLoggedInUser
{
get
{
@@ -349,6 +349,7 @@
set { createFriendshipUrl = value; }
}
+
///
/// The format of the results from the twitter API. Ex: .xml, .json, .rss, .atom. Defaults to ".xml"
///
@@ -534,7 +535,7 @@
else
{
// need to make an extra call to twitter
- User user = GetUser(userId);
+ IUser user = GetUser(userId);
friendsCount = user.FollowingCount;
}
@@ -613,9 +614,9 @@
return users;
}
- public User GetUser(int userId)
+ public IUser GetUser(int userId)
{
- User user = new User();
+ IUser user = new User();
// Twitter expects http://twitter.com/users/show/12345.xml
string requestURL = UserShowUrl + userId + Format;
@@ -674,7 +675,7 @@
/// Post new tweet to Twitter
///
/// newly added tweet
- public Tweet AddTweet(string text)
+ public ITweet AddTweet(string text)
{
bool isDirectMessage = (text.StartsWith("d ", StringComparison.CurrentCultureIgnoreCase));
@@ -811,7 +812,7 @@
/// Authenticating with the provided credentials and retrieve the user's settings
///
///
- public User Login()
+ public IUser Login()
{
string timelineUrl = UserShowUrl + username + Format;
Index: Witty/Witty.sln
===================================================================
--- Witty/Witty.sln (revision 349)
+++ Witty/Witty.sln (working copy)
@@ -13,6 +13,8 @@
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csproj", "{EA9BEE27-11D2-4346-83D9-9A056282B7B9}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IdenticaLib", "IdenticaLib\IdenticaLib.csproj", "{580176B8-7199-406C-AFFB-C8B81450AEDC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -43,6 +45,10 @@
{EA9BEE27-11D2-4346-83D9-9A056282B7B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA9BEE27-11D2-4346-83D9-9A056282B7B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA9BEE27-11D2-4346-83D9-9A056282B7B9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {580176B8-7199-406C-AFFB-C8B81450AEDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {580176B8-7199-406C-AFFB-C8B81450AEDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {580176B8-7199-406C-AFFB-C8B81450AEDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {580176B8-7199-406C-AFFB-C8B81450AEDC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE