PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/Code/Channels/Facebook/REST/FacebookRESTClient.cs

http://github.com/waseems/inbox2_desktop
C# | 611 lines | 459 code | 134 blank | 18 comment | 38 complexity | 673be70b76972466a87422bdee92469b MD5 | raw file
Possible License(s): BSD-3-Clause
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Security.Cryptography;
  7. using System.Text;
  8. using System.Web;
  9. using System.Xml.Linq;
  10. using Inbox2.Channels.Facebook.REST.DataContracts;
  11. using Inbox2.Platform.Channels;
  12. using Inbox2.Platform.Channels.Entities;
  13. using Inbox2.Platform.Channels.Extensions;
  14. using Inbox2.Platform.Channels.Text;
  15. using Inbox2.Platform.Logging;
  16. namespace Inbox2.Channels.Facebook.REST
  17. {
  18. public class FacebookRESTClient : IDisposable
  19. {
  20. protected IFacebookClient channel;
  21. protected readonly string apiKey;
  22. protected readonly string apiSecret;
  23. protected string sessionKey;
  24. protected string sessionSecret;
  25. protected string uid;
  26. public FacebookRESTClient(string apiKey, string apiSecret)
  27. : this(apiKey, apiSecret, null, null)
  28. {
  29. }
  30. public FacebookRESTClient(string apiKey, string apiSecret, string sessionKey, string sessionSecret)
  31. {
  32. // Note: use ApiSecret as the sessionSecret with web apps, desktop apps would use the SessionSecret
  33. channel = ChannelHelper.BuildChannel();
  34. this.apiKey = apiKey;
  35. this.apiSecret = apiSecret;
  36. this.sessionKey = sessionKey;
  37. this.sessionSecret = sessionSecret;
  38. }
  39. public FbAuth Authenticate()
  40. {
  41. // Allready logged in
  42. if (!String.IsNullOrEmpty(sessionKey))
  43. return FbAuth.Success;
  44. string authToken = ChannelContext.Current.ClientContext.GetSetting("/Channels/Facebook/AuthToken").ToString();
  45. if (String.IsNullOrEmpty(authToken))
  46. return FbAuth.NoAuthKey;
  47. Dictionary<string, string> requestParams = new Dictionary<string, string>();
  48. requestParams.Add("method", "auth.getSession");
  49. requestParams.Add("api_key", apiKey);
  50. requestParams.Add("v", "1.0");
  51. requestParams.Add("auth_token", authToken);
  52. var result = channel.GetSession(apiKey, GenerateSignature(requestParams, apiSecret), authToken);
  53. foreach (XElement resultElement in result.Elements())
  54. {
  55. if (resultElement.Name.LocalName.Equals("error_code"))
  56. return FbAuth.Error;
  57. if (resultElement.Name.LocalName.Equals("session_key"))
  58. sessionKey = resultElement.Value;
  59. else if (resultElement.Name.LocalName.Equals("uid"))
  60. uid = resultElement.Value;
  61. else if (resultElement.Name.LocalName.Equals("secret"))
  62. sessionSecret = resultElement.Value;
  63. }
  64. ChannelContext.Current.ClientContext.SaveSetting("/Channels/Facebook/SessionKey", sessionKey);
  65. ChannelContext.Current.ClientContext.SaveSetting("/Channels/Facebook/SessionSecret", sessionSecret);
  66. ChannelContext.Current.ClientContext.DeleteSetting("/Channels/Facebook/AuthToken");
  67. return FbAuth.Success;
  68. }
  69. public FbContact GetLoggedInUser()
  70. {
  71. Authenticate();
  72. string call_id = GetNextCallNr();
  73. Dictionary<string, string> requestParams = new Dictionary<string, string>();
  74. requestParams.Add("method", "users.getloggedinuser");
  75. requestParams.Add("api_key", apiKey);
  76. requestParams.Add("session_key", sessionKey);
  77. requestParams.Add("call_id", call_id);
  78. requestParams.Add("v", "1.0");
  79. var result = channel.GetLoggedInUser(apiKey, sessionKey, call_id, GenerateSignature(requestParams, sessionSecret));
  80. var userId = result.Value;
  81. return GetUsersInfo(userId).First();
  82. }
  83. public IEnumerable<FbContact> GetContacts()
  84. {
  85. Authenticate();
  86. string call_id = GetNextCallNr();
  87. Dictionary<string, string> requestParams = new Dictionary<string, string>();
  88. requestParams.Add("method", "friends.get");
  89. requestParams.Add("api_key", apiKey);
  90. requestParams.Add("session_key", sessionKey);
  91. requestParams.Add("call_id", call_id);
  92. requestParams.Add("v", "1.0");
  93. var result = channel.GetContacts(apiKey, sessionKey, call_id, GenerateSignature(requestParams, sessionSecret));
  94. XNamespace ns = result.GetDefaultNamespace();
  95. List<string> friendsList = result.Elements().Select(e => e.Value).ToList();
  96. for (int i = 0; i < friendsList.Count; i += 10)
  97. {
  98. // Creates lists like id1,id2,id3, etc
  99. var ids = String.Join(",", friendsList.Skip(i).Take(10).ToArray());
  100. // Now get the desired data for user's contact to define the attributes of ChannelContact object
  101. foreach (var info in GetUsersInfo(ids))
  102. {
  103. yield return info;
  104. }
  105. }
  106. }
  107. public IEnumerable<FbContact> GetUsersInfo(string ids)
  108. {
  109. Authenticate();
  110. string call_id = GetNextCallNr();
  111. Dictionary<string, string> requestParamsUserInfo = new Dictionary<string, string>();
  112. requestParamsUserInfo.Add("method", "users.getinfo");
  113. requestParamsUserInfo.Add("api_key", apiKey);
  114. requestParamsUserInfo.Add("session_key", sessionKey);
  115. requestParamsUserInfo.Add("call_id", call_id);
  116. requestParamsUserInfo.Add("v", "1.0");
  117. requestParamsUserInfo.Add("uids", ids);
  118. requestParamsUserInfo.Add("fields", "name,last_name,first_name,pic_square");
  119. XElement result = channel.GetUserInfo(apiKey, sessionKey, call_id, GenerateSignature(requestParamsUserInfo, sessionSecret), ids, "name,last_name,first_name,pic_square");
  120. XNamespace ns = result.GetDefaultNamespace();
  121. if (!IsError(result))
  122. {
  123. foreach (XElement resultElement in result.Elements())
  124. {
  125. FbContact contact = new FbContact();
  126. try
  127. {
  128. contact.UserId = resultElement.Element(ns + "uid").Value;
  129. contact.Name = resultElement.Element(ns + "name").Value;
  130. contact.Lastname = resultElement.Element(ns + "last_name").Value;
  131. contact.Firstname = resultElement.Element(ns + "first_name").Value;
  132. contact.AvatarSquareUrl = resultElement.Element(ns + "pic_square").Value;
  133. }
  134. catch (NullReferenceException e)
  135. {
  136. Logger.Error("Unable to retreive contact details for contact. Uid = {0}", LogSource.Channel, resultElement.Value);
  137. continue;
  138. }
  139. yield return contact;
  140. }
  141. }
  142. }
  143. public IEnumerable<FbMessage> GetMessages(FbMessageFolder folder)
  144. {
  145. Authenticate();
  146. string call_id = GetNextCallNr();
  147. var query = new StringBuilder();
  148. query.Append("{");
  149. query.Append("\"threads\":\"SELECT thread_id, subject, recipients, updated_time, parent_message_id, parent_thread_id, unread FROM thread WHERE folder_id = 0\",");
  150. query.Append("\"messages\":\"SELECT thread_id, body, author_id, created_time, attachment FROM message WHERE thread_id IN (SELECT thread_id FROM #threads)\",");
  151. query.Append("\"users\":\"SELECT uid, name FROM user WHERE uid IN (SELECT author_id FROM #messages)\"");
  152. query.Append("}");
  153. Dictionary<string, string> requestParams = new Dictionary<string, string>();
  154. requestParams.Add("method", "fql.multiquery");
  155. requestParams.Add("api_key", apiKey);
  156. requestParams.Add("session_key", sessionKey);
  157. requestParams.Add("call_id", call_id);
  158. requestParams.Add("v", "1.0");
  159. requestParams.Add("queries", query.ToString());
  160. var result = channel.ExecuteMultiQueries(apiKey, sessionKey, call_id, GenerateSignature(requestParams, sessionSecret), query.ToString());
  161. XNamespace ns = result.GetDefaultNamespace();
  162. // Fire one call to retreive names for all recipients in messages
  163. var uids = result.Descendants(ns + "recipients").Elements(ns + "uid").Select(n => n.Value).ToArray();
  164. var addresses = GetAddresses(String.Join(", ", uids)).ToList();
  165. foreach (XElement messageElement in result.Descendants(ns + "message"))
  166. {
  167. FbMessage message = new FbMessage();
  168. try
  169. {
  170. var threadid = messageElement.Element(ns + "thread_id").Value;
  171. var from = messageElement.Element(ns + "author_id").Value;
  172. //if (String.IsNullOrEmpty(from))
  173. // System.Diagnostics.Debugger.Break();
  174. // Find the associated thread which contains the subject and readstate
  175. var threadElement = result.Descendants(ns + "thread").FirstOrDefault(t => t.Element(ns + "thread_id").Value == threadid);
  176. var senderElement = result.Descendants(ns + "user").FirstOrDefault(u => u.Element(ns + "uid").Value == from);
  177. if (threadElement == null || senderElement == null)
  178. {
  179. Logger.Error("Unable to determine sender for Facebook message, ignoring", LogSource.Channel);
  180. continue;
  181. }
  182. message.ThreadId = threadid;
  183. message.Subject = threadElement.Element(ns + "subject").Value;
  184. message.Body = messageElement.Element(ns + "body").Value;
  185. message.From = new SourceAddress(senderElement.Element(ns + "uid").Value, senderElement.Element(ns + "name").Value);
  186. message.To = new SourceAddressCollection();
  187. message.Read = threadElement.Element(ns + "unread").Value == "0";
  188. message.DateCreated = Int64.Parse(threadElement.Element(ns + "updated_time").Value).ToUnixTime();
  189. foreach (XElement recipientElement in threadElement.Element(ns + "recipients").Elements())
  190. message.To.Add(addresses.FirstOrDefault(a => a.Address == recipientElement.Value));
  191. message.MessageId = message.ThreadId + message.From.Address;
  192. }
  193. catch (Exception ex)
  194. {
  195. Logger.Error("Unable to retreive mesaage. Result = {0}. Exception = {1}", LogSource.Channel, messageElement.Value, ex);
  196. continue;
  197. }
  198. yield return message;
  199. }
  200. }
  201. public IEnumerable<SourceAddress> GetAddresses(string uids)
  202. {
  203. Authenticate();
  204. string call_id = GetNextCallNr();
  205. string query = String.Format("SELECT uid, name FROM user WHERE uid IN ({0})", uids);
  206. Dictionary<string, string> requestParams = new Dictionary<string, string>();
  207. requestParams.Add("method", "fql.query");
  208. requestParams.Add("api_key", apiKey);
  209. requestParams.Add("session_key", sessionKey);
  210. requestParams.Add("call_id", call_id);
  211. requestParams.Add("v", "1.0");
  212. requestParams.Add("query", query);
  213. var result = channel.ExecuteQuery(apiKey, sessionKey, call_id, GenerateSignature(requestParams, sessionSecret), query);
  214. XNamespace ns = result.GetDefaultNamespace();
  215. foreach (XElement element in result.Descendants(ns + "user"))
  216. {
  217. SourceAddress address;
  218. try
  219. {
  220. address = new SourceAddress(element.Element(ns + "uid").Value, element.Element(ns + "name").Value);
  221. }
  222. catch (Exception ex)
  223. {
  224. Logger.Error("Unable to retreive user source address. Result = {0}. Exception = {1}", LogSource.Channel, element.Value, ex);
  225. continue;
  226. }
  227. yield return address;
  228. }
  229. }
  230. public IEnumerable<FbStatus> GetStatusses(int pageSize)
  231. {
  232. Authenticate();
  233. string call_id = GetNextCallNr();
  234. string limit = pageSize.ToString();
  235. Dictionary<string, string> requestParams = new Dictionary<string, string>();
  236. requestParams.Add("method", "stream.get");
  237. requestParams.Add("api_key", apiKey);
  238. requestParams.Add("session_key", sessionKey);
  239. requestParams.Add("call_id", call_id);
  240. requestParams.Add("source_ids", "");
  241. requestParams.Add("v", "1.0");
  242. requestParams.Add("limit", limit);
  243. var result = channel.GetStream(apiKey, sessionKey, call_id, GenerateSignature(requestParams, sessionSecret), "", limit);
  244. XNamespace ns = result.GetDefaultNamespace();
  245. foreach (XElement element in result.Descendants(ns + "stream_post"))
  246. {
  247. var status = new FbStatus();
  248. try
  249. {
  250. var id = element.Element(ns + "actor_id").Value;
  251. var userElement = result.Descendants(ns + "profile").First(p => p.Element(ns + "id").Value == id);
  252. status.From = new SourceAddress(id, userElement.Element(ns + "name").Value,
  253. userElement.Element(ns + "pic_square").Value);
  254. if (element.Element(ns + "target_id") != null && !String.IsNullOrEmpty(element.Element(ns + "target_id").Value))
  255. {
  256. var toid = element.Element(ns + "target_id").Value;
  257. if (!String.IsNullOrEmpty(toid))
  258. {
  259. var toUserElement = result.Descendants(ns + "profile").First(p => p.Element(ns + "id").Value == toid);
  260. status.To = new SourceAddress(toid, toUserElement.Element(ns + "name").Value,
  261. toUserElement.Element(ns + "pic_square").Value);
  262. }
  263. }
  264. status.Uid = Int64.Parse(element.Element(ns + "actor_id").Value);
  265. status.StatusId = element.Element(ns + "post_id").Value;
  266. status.Message = element.Element(ns + "message").Value;
  267. status.DateCreated = Int64.Parse(element.Element(ns + "created_time").Value).ToUnixTime();
  268. foreach (var commentElement in element.Descendants(ns + "comment"))
  269. {
  270. var comment = new FbStatus();
  271. var commentid = commentElement.Element(ns + "fromid").Value;
  272. var commentUserElement = result.Descendants(ns + "profile").First(p => p.Element(ns + "id").Value == commentid);
  273. comment.From = new SourceAddress(commentid, commentUserElement.Element(ns + "name").Value,
  274. commentUserElement.Element(ns + "pic_square").Value);
  275. comment.Uid = Int64.Parse(commentElement.Element(ns + "fromid").Value);
  276. comment.StatusId = commentElement.Element(ns + "id").Value;
  277. comment.Message = commentElement.Element(ns + "text").Value;
  278. comment.DateCreated = Int64.Parse(commentElement.Element(ns + "time").Value).ToUnixTime();
  279. status.Comments.Add(comment);
  280. }
  281. foreach (var attachmentElement in element.Descendants(ns + "stream_media"))
  282. {
  283. var attachment = new FbAttachment();
  284. attachment.MediaType = (FbMediaType)Enum.Parse(typeof(FbMediaType), attachmentElement.Element(ns + "type").Value, true);
  285. switch (attachment.MediaType)
  286. {
  287. case FbMediaType.Link:
  288. {
  289. attachment.TargetUrl = HttpUtility.HtmlDecode(attachmentElement.Element(ns + "href").Value);
  290. attachment.PreviewAltText = HttpUtility.HtmlDecode(attachmentElement.Element(ns + "alt").Value);
  291. attachment.PreviewImageUrl = HttpUtility.HtmlDecode(attachmentElement.Element(ns + "src").Value);
  292. break;
  293. }
  294. case FbMediaType.Photo:
  295. {
  296. attachment.TargetUrl = HttpUtility.HtmlDecode(attachmentElement.Element(ns + "href").Value);
  297. attachment.PreviewAltText = HttpUtility.HtmlDecode(attachmentElement.Element(ns + "alt").Value);
  298. attachment.PreviewImageUrl = HttpUtility.HtmlDecode(attachmentElement.Element(ns + "src").Value);
  299. break;
  300. }
  301. case FbMediaType.Video:
  302. {
  303. var src = new Uri(attachmentElement.Element(ns + "src").Value);
  304. var uriParams = NameValueParser.GetCollection(src.Query, "&");
  305. attachment.TargetUrl = HttpUtility.HtmlDecode(attachmentElement.Element(ns + "video").Element(ns + "display_url").Value);
  306. attachment.PreviewAltText = HttpUtility.HtmlDecode(attachmentElement.Element(ns + "alt").Value);
  307. attachment.PreviewImageUrl = HttpUtility.UrlDecode(uriParams["url"]);
  308. break;
  309. }
  310. }
  311. status.Attachments.Add(attachment);
  312. }
  313. }
  314. catch (Exception ex)
  315. {
  316. Logger.Error("Unable to retreive user source address. Result = {0}. Exception = {1}", LogSource.Channel, element.Value, ex);
  317. continue;
  318. }
  319. yield return status;
  320. }
  321. }
  322. public IEnumerable<FbStatus> GetStatusses(string userid, int pageSize)
  323. {
  324. Authenticate();
  325. string call_id = GetNextCallNr();
  326. string limit = pageSize.ToString();
  327. Dictionary<string, string> requestParams = new Dictionary<string, string>();
  328. requestParams.Add("method", "stream.get");
  329. requestParams.Add("api_key", apiKey);
  330. requestParams.Add("session_key", sessionKey);
  331. requestParams.Add("call_id", call_id);
  332. requestParams.Add("source_ids", userid);
  333. requestParams.Add("v", "1.0");
  334. requestParams.Add("limit", limit);
  335. var result = channel.GetStream(apiKey, sessionKey, call_id, GenerateSignature(requestParams, sessionSecret), userid, limit);
  336. XNamespace ns = result.GetDefaultNamespace();
  337. foreach (XElement element in result.Descendants(ns + "stream_post"))
  338. {
  339. var status = new FbStatus();
  340. string id;
  341. try
  342. {
  343. id = element.Element(ns + "actor_id").Value;
  344. var userElement = result.Descendants(ns + "profile").First(p => p.Element(ns + "id").Value == id);
  345. status.From = new SourceAddress(id, userElement.Element(ns + "name").Value,
  346. userElement.Element(ns + "pic_square").Value);
  347. if (element.Element(ns + "target_id") != null && !String.IsNullOrEmpty(element.Element(ns + "target_id").Value))
  348. {
  349. var toid = element.Element(ns + "target_id").Value;
  350. if (!String.IsNullOrEmpty(toid))
  351. {
  352. var toUserElement = result.Descendants(ns + "profile").First(p => p.Element(ns + "id").Value == toid);
  353. status.To = new SourceAddress(toid, toUserElement.Element(ns + "name").Value,
  354. toUserElement.Element(ns + "pic_square").Value);
  355. }
  356. }
  357. status.Uid = Int64.Parse(element.Element(ns + "actor_id").Value);
  358. status.StatusId = element.Element(ns + "post_id").Value;
  359. status.Message = element.Element(ns + "message").Value;
  360. status.DateCreated = Int64.Parse(element.Element(ns + "created_time").Value).ToUnixTime();
  361. }
  362. catch (Exception ex)
  363. {
  364. Logger.Error("Unable to retreive user source address. Result = {0}. Exception = {1}", LogSource.Channel, element.Value, ex);
  365. continue;
  366. }
  367. // If id and actorid are not equal then this is a message directed at user we are
  368. // looking at by someone else, in that case skip it as we are only interested in updates
  369. // by this specific user.
  370. if (userid == id)
  371. yield return status;
  372. }
  373. }
  374. public void SendNotification(string to, string notification)
  375. {
  376. Authenticate();
  377. string call_id = GetNextCallNr();
  378. // Now get the desired data for user's contact to define the attributes of ChannelContact object
  379. Dictionary<string, string> requestParams = new Dictionary<string, string>();
  380. requestParams.Add("method", "notifications.send");
  381. requestParams.Add("api_key", apiKey);
  382. requestParams.Add("session_key", sessionKey);
  383. requestParams.Add("call_id", call_id);
  384. requestParams.Add("v", "1.0");
  385. requestParams.Add("to_ids", to);
  386. requestParams.Add("notification", notification);
  387. //var result = channel.SendNotification(apiKey, sessionKey, call_id, GenerateSignature(requestParams, sessionSecret), to, notification);
  388. var queryString =
  389. String.Format(
  390. "?method=notifications.send&api_key={0}&session_key={1}&call_id={2}&sig={3}&v=1.0&to_ids={4}&notification={5}",
  391. apiKey, sessionKey, call_id, GenerateSignature(requestParams, sessionSecret), to, notification);
  392. WebClient wc = new WebClient();
  393. wc.DownloadString("http://api.facebook.com/restserver.php" + queryString);
  394. }
  395. public void SetStatus(string status)
  396. {
  397. Authenticate();
  398. string call_id = GetNextCallNr();
  399. Dictionary<string, string> requestParams = new Dictionary<string, string>();
  400. requestParams.Add("method", "status.set");
  401. requestParams.Add("api_key", apiKey);
  402. requestParams.Add("session_key", sessionKey);
  403. requestParams.Add("call_id", call_id);
  404. requestParams.Add("v", "1.0");
  405. requestParams.Add("status", status);
  406. var result = channel.SetStatus(apiKey, sessionKey, call_id, GenerateSignature(requestParams, sessionSecret), status);
  407. if (IsError(result))
  408. {
  409. Logger.Error("Unable to update status. Error = {0}", LogSource.Channel, result);
  410. }
  411. }
  412. public void PostComment(string comment, string inReplyTo)
  413. {
  414. Authenticate();
  415. string call_id = GetNextCallNr();
  416. Dictionary<string, string> requestParams = new Dictionary<string, string>();
  417. requestParams.Add("method", "stream.addcomment");
  418. requestParams.Add("api_key", apiKey);
  419. requestParams.Add("session_key", sessionKey);
  420. requestParams.Add("call_id", call_id);
  421. requestParams.Add("v", "1.0");
  422. requestParams.Add("post_id", inReplyTo);
  423. requestParams.Add("comment", comment);
  424. var result = channel.AddComment(apiKey, sessionKey, call_id, GenerateSignature(requestParams, sessionSecret), inReplyTo, comment);
  425. if (IsError(result))
  426. {
  427. Logger.Error("Unable to update status. Error = {0}", LogSource.Channel, result);
  428. }
  429. }
  430. public void Dispose()
  431. {
  432. ((IDisposable)channel).Dispose();
  433. channel = null;
  434. }
  435. #region Helper methods
  436. bool IsError(XElement resultElement)
  437. {
  438. var ns = resultElement.GetDefaultNamespace();
  439. return (resultElement.Element(ns + "error_code") != null);
  440. }
  441. string GenerateSignature(IDictionary<string, string> args, string currentSecret)
  442. {
  443. SortedDictionary<string, string> sortedD = new SortedDictionary<string, string>(args);
  444. List<string> argsAsKvpStrings = ConvertParameterDictionaryToList(sortedD);
  445. StringBuilder signatureBuilder = new StringBuilder();
  446. // Append all the parameters to the signature input paramaters
  447. foreach (string s in argsAsKvpStrings)
  448. signatureBuilder.Append(s);
  449. // Append the secret to the signature builder
  450. signatureBuilder.Append(currentSecret);
  451. byte[] hash;
  452. MD5 md5 = MD5.Create();
  453. // Compute the MD5 hash of the signature builder
  454. hash = md5.ComputeHash(Encoding.UTF8.GetBytes(signatureBuilder.ToString()));
  455. // Reinitialize the signature builder to store the actual signature
  456. signatureBuilder = new StringBuilder();
  457. // Append the hash to the signature
  458. for (int i = 0; i < hash.Length; i++)
  459. signatureBuilder.Append(hash[i].ToString("x2", CultureInfo.InvariantCulture));
  460. return signatureBuilder.ToString();
  461. }
  462. List<string> ConvertParameterDictionaryToList(IDictionary<string, string> parameterDictionary)
  463. {
  464. List<string> parameters = new List<string>();
  465. foreach (KeyValuePair<string, string> kvp in parameterDictionary)
  466. {
  467. parameters.Add(String.Format(CultureInfo.InvariantCulture, "{0}={1}", kvp.Key, kvp.Value));
  468. }
  469. return parameters;
  470. }
  471. string GetNextCallNr()
  472. {
  473. return DateTime.Now.Ticks.ToString();
  474. }
  475. #endregion
  476. }
  477. }