// Copyright 2008 FriendFeed // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Text; using System.Web; using System.Xml; namespace FriendFeed { /// /// A client to interact with the FriendFeed API. /// /// More information about the API is available at http://friendfeed.com/api/. /// class FriendFeedClient { private string nickname_; private string remoteKey_; /// /// Creates an un-authenticated FriendFeed API client. /// /// An un-authenticated client can only perform read operations on public /// feeds. /// public FriendFeedClient() { } /// /// Creates a FriendFeed API client authenticated with the given credentials. /// public FriendFeedClient(string nickname, string remoteKey) { nickname_ = nickname; remoteKey_ = remoteKey; } /// /// Publishes the given message to the authenticated user's feed. /// /// The text of the message /// The new entry as returned by the server public Entry PublishMessage(string message) { return PublishLink(message, null); } /// /// Publishes the given link to the authenticated user's feed. /// /// The title of the link /// The link URL /// The new entry as returned by the server public Entry PublishLink(string title, string link) { return PublishLink(title, link, null); } /// /// Publishes the given link to the authenticated user's feed. /// /// The title of the link /// The link URL /// The initial comment for this entry /// The new entry as returned by the server public Entry PublishLink(string title, string link, string comment) { return PublishLink(title, link, comment, null); } /// /// Publishes the given link to the authenticated user's feed. /// /// The title of the link /// The link URL /// The initial comment for this entry /// URLs of the thumbnails to be included with this entry /// The new entry as returned by the server public Entry PublishLink(string title, string link, string comment, ThumbnailUrl[] imageUrls) { return PublishLink(title, link, comment, imageUrls, null); } /// /// Publishes the given link to the authenticated user's feed. /// /// The title of the link /// The link URL /// The initial comment for this entry /// URLs of the thumbnails to be included with this entry /// Paths to local image files to be included as thumbnails with this entry /// The new entry as returned by the server public Entry PublishLink(string title, string link, string comment, ThumbnailUrl[] imageUrls, ThumbnailFile[] imageFiles) { SortedDictionary postArguments = new SortedDictionary(); postArguments["title"] = title; if (link != null) { postArguments["link"] = link; } if (comment != null) { postArguments["comment"] = comment; } if (imageUrls != null) { for (int i = 0; i < imageUrls.Length; i++) { postArguments["image" + i + "_url"] = imageUrls[i].Url; if (imageUrls[i].Link != null) { postArguments["image" + i + "_link"] = imageUrls[i].Url; } } } SortedDictionary fileAttachments = new SortedDictionary(); if (imageFiles != null) { for (int i = 0; i < imageFiles.Length; i++) { fileAttachments["file" + i] = imageFiles[i].Path; if (imageFiles[i].Link != null) { postArguments["file" + i + "_link"] = imageUrls[i].Url; } } } HttpWebResponse response = MakeRequest("/api/share", null, postArguments, fileAttachments); XmlDocument document = new XmlDocument(); document.Load(response.GetResponseStream()); return (new Feed(document.DocumentElement)).Entries[0]; } /// /// Returns the most recent entries from all publicly visible users. /// public Feed FetchPublicFeed() { return FetchPublicFeed(null); } public Feed FetchPublicFeed(string service) { return FetchPublicFeed(null, 0); } public Feed FetchPublicFeed(string service, int start) { return FetchPublicFeed(null, 0, 30); } public Feed FetchPublicFeed(string service, int start, int num) { return FetchFeed("/api/feed/public", service, start, num); } /// /// Returns the most recent entries from the authenticated user's /// subscriptions, as they would see on their FriendFeed home page. /// public Feed FetchHomeFeed() { return FetchHomeFeed(null); } public Feed FetchHomeFeed(string service) { return FetchHomeFeed(null, 0); } public Feed FetchHomeFeed(string service, int start) { return FetchHomeFeed(null, 0, 30); } public Feed FetchHomeFeed(string service, int start, int num) { return FetchFeed("/api/feed/home", service, start, num); } /// /// Fetches the most recent entries for the user with the given nickname. /// /// If the user has a private feed, authentication is required. /// public Feed FetchUserFeed(string nickname) { return FetchUserFeed(nickname, null); } public Feed FetchUserFeed(string nickname, string service) { return FetchUserFeed(nickname, null, 0); } public Feed FetchUserFeed(string nickname, string service, int start) { return FetchUserFeed(nickname, null, 0, 30); } public Feed FetchUserFeed(string nickname, string service, int start, int num) { return FetchFeed("/api/feed/user/" + HttpUtility.UrlEncode(nickname), service, start, num); } /// /// Fetches the most recent entries for the given list of users. /// /// If any of the users has a private feed, authentication is required. /// public Feed FetchMultiUserFeed(string[] nicknames) { return FetchMultiUserFeed(nicknames, null); } public Feed FetchMultiUserFeed(string[] nicknames, string service) { return FetchMultiUserFeed(nicknames, null, 0); } public Feed FetchMultiUserFeed(string[] nicknames, string service, int start) { return FetchMultiUserFeed(nicknames, null, 0, 30); } public Feed FetchMultiUserFeed(string[] nicknames, string service, int start, int num) { SortedDictionary urlArguments = new SortedDictionary(); urlArguments["nickname"] = string.Join(",", nicknames); return FetchFeed("/api/feed/user", service, start, num, urlArguments); } /// /// Fetches the feed at the given path, parsing and returning the result. /// public Feed FetchFeed(string path, string service, int start, int num) { return FetchFeed(path, service, start, num, null); } /// /// Fetches the feed at the given path with the given URL arguments. /// public Feed FetchFeed(string path, string service, int start, int num, SortedDictionary urlArguments) { if (urlArguments == null) urlArguments = new SortedDictionary(); if (service != null) urlArguments["service"] = service; urlArguments["start"] = start.ToString(); urlArguments["num"] = num.ToString(); HttpWebResponse response = MakeRequest(path, urlArguments, null, null); XmlDocument document = new XmlDocument(); document.Load(response.GetResponseStream()); return new Feed(document.DocumentElement); } /// /// Makes an HTTP request to the FriendFeed servers. /// /// If this client was created with a nickname and remote key, the request /// is automatically authenticated. If postArguments is given, the request /// will be a POST request. We send a GET otherwise. /// /// The path for the request, e.g., /api/feed/home /// The arguments to be included in the URL, e.g., {"start": "0"} /// The arguments to be included in the POST body of the request /// Files to be uploaded with this request public HttpWebResponse MakeRequest(string path, SortedDictionary urlArguments, SortedDictionary postArguments, SortedDictionary fileAttachments) { if (urlArguments == null) urlArguments = new SortedDictionary(); urlArguments["format"] = "xml"; string url = "http://friendfeed.com" + path + "?" + UrlEncode(urlArguments); HttpWebRequest request = (HttpWebRequest) WebRequest.Create(url); // Encode the POST body if POST args are given if (postArguments != null) { request.Method = "POST"; string boundary = Guid.NewGuid().ToString().Replace("-", ""); request.ContentType = "multipart/form-data; boundary=" + boundary; Stream stream = request.GetRequestStream(); foreach (KeyValuePair pair in postArguments) { byte[] value = Encoding.UTF8.GetBytes(string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n", boundary, pair.Key, pair.Value)); stream.Write(value, 0, value.Length); } foreach (KeyValuePair pair in fileAttachments) { FileInfo info = new FileInfo(pair.Value); byte[] contents = new byte[info.Length]; using (FileStream file = File.OpenRead(pair.Value)) { file.Read(contents, 0, contents.Length); } byte[] value = Encoding.UTF8.GetBytes(string.Format("--{0}\r\nContent-Disposition: file; name=\"{1}\"; filename=\"{2}\"\r\n\r\n", boundary, pair.Key, Path.GetFileName(pair.Value))); stream.Write(value, 0, value.Length); stream.Write(contents, 0, contents.Length); stream.Write(Encoding.UTF8.GetBytes("\r\n"), 0, 2); } byte[] end = Encoding.UTF8.GetBytes(string.Format("--{0}--\r\n", boundary)); stream.Write(end, 0, end.Length); stream.Close(); } else { request.Method = "GET"; } // Add the HTTP Basic auth header if we have credentials if (nickname_ != null && remoteKey_ != null) { CredentialCache cache = new CredentialCache(); cache.Add(new Uri("http://friendfeed.com/api/"), "Basic", new NetworkCredential(nickname_, remoteKey_)); request.Credentials = cache; } System.Diagnostics.Debug.WriteLine("Downloading " + url); request.UserAgent = "FriendFeedCSharpApi/0.4"; return (HttpWebResponse) request.GetResponse(); } private string UrlEncode(SortedDictionary arguments) { string[] parts = new string[arguments.Count]; int i = 0; foreach (KeyValuePair pair in arguments) { parts[i++] = HttpUtility.UrlEncode(pair.Key) + "=" + HttpUtility.UrlEncode(pair.Value); } return string.Join("&", parts); } } class ThumbnailUrl { public string Url; public string Link; /// /// Creates an entry thumbnail from the image at the given URL. /// /// The thumbnail will link to the main entry link. If you want the /// thumbnail to link to a different URL, you can specify a link in /// the other constructor. /// public ThumbnailUrl(string url) { this.Url = url; } /// /// Creates an entry thumbnail from the image at the given URL. /// /// The thumbnail image will link to the given link URL. /// public ThumbnailUrl(string url, string link) { this.Url = url; this.Link = link; } } class ThumbnailFile { public string Path; public string Link; /// /// Creates an entry thumbnail from the file at the given path. /// /// The thumbnail will link to the main entry link. If you want the /// thumbnail to link to a different URL, you can specify a link in /// the other constructor. /// public ThumbnailFile(string path) { this.Path = path; } /// /// Creates an entry thumbnail from the file at the given path. /// /// The thumbnail image will link to the given link URL. /// public ThumbnailFile(string path, string link) { this.Path = path; this.Link = link; } } class Feed { public List Entries; public Feed(XmlElement element) { Entries = new List(); foreach (XmlElement child in element.ChildNodes) { Entries.Add(new Entry(child)); } } } class Entry { public string Id; public string Title; public string Link; public DateTime Published; public DateTime Updated; public User User; public Service Service; public List Comments; public List Likes; public List Media; public Entry(XmlElement element) { Id = Util.ChildValue(element, "id"); Title = Util.ChildValue(element, "title"); Link = Util.ChildValue(element, "link"); Published = DateTime.Parse(Util.ChildValue(element, "published")); Updated = DateTime.Parse(Util.ChildValue(element, "updated")); User = new User(Util.ChildElement(element, "user")); Service = new Service(Util.ChildElement(element, "user")); Comments = new List(); foreach (XmlElement child in element.GetElementsByTagName("comment")) { Comments.Add(new Comment(child)); } Likes = new List(); foreach (XmlElement child in element.GetElementsByTagName("like")) { Likes.Add(new Like(child)); } Media = new List(); foreach (XmlElement child in element.GetElementsByTagName("media")) { Media.Add(new Media(child)); } } } class User { public string Id; public string Nickname; public string ProfileUrl; public User(XmlElement element) { Id = Util.ChildValue(element, "id"); Nickname = Util.ChildValue(element, "nickname"); ProfileUrl = Util.ChildValue(element, "profileUrl"); } } class Service { public string Id; public string Name; public string IconUrl; public string ProfileUrl; public Service(XmlElement element) { Id = Util.ChildValue(element, "id"); Name = Util.ChildValue(element, "name"); IconUrl = Util.ChildValue(element, "iconUrl"); ProfileUrl = Util.ChildValue(element, "profileUrl"); } } class Like { public DateTime Date; public User User; public Like(XmlElement element) { Date = DateTime.Parse(Util.ChildValue(element, "date")); User = new User(Util.ChildElement(element, "user")); } } class Comment { public DateTime Date; public User User; public string Body; public Comment(XmlElement element) { Date = DateTime.Parse(Util.ChildValue(element, "date")); User = new User(Util.ChildElement(element, "user")); Body = Util.ChildValue(element, "body"); } } class Media { public string Title; public string Player; public List Thumbnails; public List Content; public Media(XmlElement element) { Title = Util.ChildValue(element, "title"); Player = Util.ChildValue(element, "player"); Thumbnails = new List(); foreach (XmlElement child in element.GetElementsByTagName("thumbnail")) { Thumbnails.Add(new Thumbnail(child)); } Content = new List(); foreach (XmlElement child in element.GetElementsByTagName("content")) { Content.Add(new Content(child)); } } } class Thumbnail { public string Url; public string Width; public string Height; public Thumbnail(XmlElement element) { Url = Util.ChildValue(element, "url"); Width = Util.ChildValue(element, "width"); Height = Util.ChildValue(element, "height"); } } class Content { public string Url; public string Type; public string Width; public string Height; public Content(XmlElement element) { Url = Util.ChildValue(element, "url"); Type = Util.ChildValue(element, "type"); Width = Util.ChildValue(element, "width"); Height = Util.ChildValue(element, "height"); } } static internal class Util { public static XmlElement ChildElement(XmlElement element, string name) { XmlNodeList list = element.GetElementsByTagName(name); foreach (XmlElement child in list) { if (child.ParentNode == element) { return child; } } return null; } public static string ChildValue(XmlElement element, string name) { XmlElement child = ChildElement(element, name); return (child == null) ? null : child.InnerText; } } }