PageRenderTime 101ms CodeModel.GetById 27ms RepoModel.GetById 2ms app.codeStats 0ms

/BaconographyPortable/Model/Reddit/RedditService.cs

https://github.com/hippiehunter/Baconography
C# | 1192 lines | 995 code | 183 blank | 14 comment | 126 complexity | 04c9251ae744a8750eeb5f04111b5413 MD5 | raw file
  1. using BaconographyPortable.Properties;
  2. using BaconographyPortable.Services;
  3. using BaconographyPortable.ViewModel;
  4. using Microsoft.Practices.ServiceLocation;
  5. using Newtonsoft.Json;
  6. using Newtonsoft.Json.Linq;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Net;
  11. using System.Text;
  12. using System.Threading.Tasks;
  13. namespace BaconographyPortable.Model.Reddit
  14. {
  15. public class RedditService : IRedditService
  16. {
  17. protected ISettingsService _settingsService;
  18. protected ISimpleHttpService _simpleHttpService;
  19. protected IUserService _userService;
  20. protected INotificationService _notificationService;
  21. protected IBaconProvider _baconProvider;
  22. Dictionary<string, string> _linkToOpMap = new Dictionary<string, string>();
  23. Dictionary<string, HashSet<string>> _subredditToModMap = new Dictionary<string, HashSet<string>>();
  24. public virtual void Initialize(ISettingsService settingsService, ISimpleHttpService simpleHttpService, IUserService userService, INotificationService notificationService, IBaconProvider baconProvider)
  25. {
  26. _settingsService = settingsService;
  27. _simpleHttpService = simpleHttpService;
  28. _userService = userService;
  29. _notificationService = notificationService;
  30. _baconProvider = baconProvider;
  31. }
  32. public async Task<Account> GetMe()
  33. {
  34. var user = await _userService.GetUser();
  35. return await GetMe(user);
  36. }
  37. //this one is seperated out so we can use it interally on initial user login
  38. public async Task<Account> GetMe(User user)
  39. {
  40. bool needsRetry = false;
  41. try
  42. {
  43. var meString = await _simpleHttpService.SendGet(user.LoginCookie, "http://www.reddit.com/api/me.json");
  44. if (!string.IsNullOrWhiteSpace(meString) && meString != "{}")
  45. {
  46. var thing = JsonConvert.DeserializeObject<Thing>(meString);
  47. return (new TypedThing<Account>(thing)).Data;
  48. }
  49. else
  50. return null;
  51. }
  52. catch (WebException webException)
  53. {
  54. if (webException.Status == WebExceptionStatus.RequestCanceled)
  55. needsRetry = true;
  56. else
  57. {
  58. _notificationService.CreateErrorNotification(webException);
  59. return null;
  60. }
  61. }
  62. catch (Exception ex)
  63. {
  64. _notificationService.CreateErrorNotification(ex);
  65. return null;
  66. }
  67. if (needsRetry)
  68. {
  69. return await GetMe();
  70. }
  71. else
  72. return null;
  73. }
  74. public async Task<User> Login(string username, string password)
  75. {
  76. var loginUri = "https://ssl.reddit.com/api/login";
  77. var postContent = new Dictionary<string, string>
  78. {
  79. { "api_type", "json" },
  80. { "user", username },
  81. { "passwd", password }
  82. };
  83. var loginResult = await _simpleHttpService.SendPostForCookies(postContent, loginUri);
  84. var jsonResult = loginResult.Item1;
  85. var loginResultThing = JsonConvert.DeserializeObject<LoginJsonThing>(jsonResult);
  86. if (loginResultThing == null || loginResultThing.Json == null ||
  87. (loginResultThing.Json.Errors != null && loginResultThing.Json.Errors.Length != 0))
  88. {
  89. _notificationService.CreateNotification(string.Format("Failed to login as User:{0}", username));
  90. return null; //errors in the login process
  91. }
  92. else
  93. {
  94. var loginCookie = loginResult.Item2["reddit_session"];
  95. var user = new User { Authenticated = true, LoginCookie = loginCookie, Username = username, NeedsCaptcha = false };
  96. user.Me = await GetMe(user);
  97. return user;
  98. }
  99. }
  100. public async Task<Listing> Search(string query, int? limit, bool reddits, string restrictedToSubreddit)
  101. {
  102. var maxLimit = (await UserIsGold()) ? 1500 : 100;
  103. var guardedLimit = Math.Min(maxLimit, limit ?? maxLimit);
  104. string targetUri = null;
  105. if (reddits)
  106. {
  107. targetUri = string.Format("http://www.reddit.com/subreddits/search.json?limit={0}&q={1}", guardedLimit, query);
  108. }
  109. else if (string.IsNullOrWhiteSpace(restrictedToSubreddit))
  110. {
  111. targetUri = string.Format("http://www.reddit.com/search.json?limit={0}&q={1}", guardedLimit, query);
  112. }
  113. else
  114. {
  115. targetUri = string.Format("http://www.reddit.com/r/{2}/search.json?limit={0}&q={1}&restrict_sr=on", guardedLimit, query, restrictedToSubreddit);
  116. }
  117. var comments = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  118. var newListing = JsonConvert.DeserializeObject<Listing>(comments);
  119. return MaybeFilterForNSFW(newListing);
  120. }
  121. public async Task<Thing> GetThingById(string id)
  122. {
  123. var targetUri = string.Format("http://www.reddit.com/by_id/{0}.json", id);
  124. try
  125. {
  126. var thingStr = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  127. if(thingStr.StartsWith("{\"kind\": \"Listing\""))
  128. {
  129. var listing = JsonConvert.DeserializeObject<Listing>(thingStr);
  130. return listing.Data.Children.First();
  131. }
  132. else
  133. return JsonConvert.DeserializeObject<Thing>(thingStr);
  134. }
  135. catch (Exception ex)
  136. {
  137. _notificationService.CreateErrorNotification(ex);
  138. return null;
  139. }
  140. }
  141. public async Task<HashSet<string>> GetSubscribedSubreddits()
  142. {
  143. var hashifyListing = new Func<Thing, string>((thing) =>
  144. {
  145. if (thing.Data is Subreddit)
  146. {
  147. return ((Subreddit)thing.Data).Name;
  148. }
  149. else
  150. return null;
  151. });
  152. return new HashSet<string>((await GetSubscribedSubredditListing())
  153. .Data.Children.Select(hashifyListing)
  154. .Where(str => str != null));
  155. }
  156. public virtual async Task<Listing> GetSubreddits(int? limit)
  157. {
  158. var maxLimit = (await UserIsGold()) ? 1500 : 100;
  159. var guardedLimit = Math.Min(maxLimit, limit ?? maxLimit);
  160. var targetUri = string.Format("http://www.reddit.com/reddits/.json?limit={0}", guardedLimit);
  161. try
  162. {
  163. var comments = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  164. var newListing = JsonConvert.DeserializeObject<Listing>(comments);
  165. return MaybeFilterForNSFW(newListing);
  166. }
  167. catch (Exception ex)
  168. {
  169. _notificationService.CreateErrorNotification(ex);
  170. return new Listing { Kind = "Listing", Data = new ListingData { Children = new List<Thing>() } };
  171. }
  172. }
  173. public async Task<TypedThing<Subreddit>> GetSubreddit(string name)
  174. {
  175. //no info for the front page
  176. if (name == "/")
  177. return new TypedThing<Subreddit>(new Thing { Kind = "t5", Data = new Subreddit { Headertitle = name } });
  178. else if (name == "all")
  179. return new TypedThing<Subreddit>(new Thing { Kind = "t5", Data = new Subreddit { Headertitle = "all", Url = "/r/all", Name = "all", DisplayName="all", Title="all", Id="t5_fakeid" } });
  180. string targetUri;
  181. if (!name.Contains("/m/"))
  182. {
  183. targetUri = string.Format("http://www.reddit.com/r/{0}/about.json", name);
  184. try
  185. {
  186. var comments = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  187. //error page
  188. if (comments.ToLower().StartsWith("<!doctype html>"))
  189. {
  190. return new TypedThing<Subreddit>(new Thing { Kind = "t5", Data = new Subreddit { Headertitle = name, Title = name, Url = string.Format("r/{0}", name), Created = DateTime.Now, CreatedUTC = DateTime.UtcNow, DisplayName = name, Description = "there doesnt seem to be anything here", Name = name, Over18 = false, PublicDescription = "there doesnt seem to be anything here", Subscribers = 0 } });
  191. }
  192. else
  193. {
  194. return new TypedThing<Subreddit>(JsonConvert.DeserializeObject<Thing>(comments));
  195. }
  196. }
  197. catch (Exception ex)
  198. {
  199. //_notificationService.CreateErrorNotification(ex);
  200. return new TypedThing<Subreddit>(new Thing { Kind = "t5", Data = new Subreddit { Headertitle = name, Title = name, Url = string.Format("r/{0}", name), Created = DateTime.Now, CreatedUTC = DateTime.UtcNow, DisplayName = name, Description = "there doesnt seem to be anything here", Name = name, Over18 = false, PublicDescription = "there doesnt seem to be anything here", Subscribers = 0 } });
  201. }
  202. }
  203. else
  204. {
  205. var currentUser = await _userService.GetUser();
  206. if (name.StartsWith("/"))
  207. name = name.TrimStart('/');
  208. if (name.StartsWith("me/"))
  209. {
  210. name = name.Replace("me/", "user/" + currentUser.Username + "/");
  211. }
  212. targetUri = string.Format("http://www.reddit.com/api/multi/{0}.json", name);
  213. try
  214. {
  215. var comments = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  216. //error page
  217. if (comments.ToLower().StartsWith("<!doctype html>"))
  218. {
  219. return new TypedThing<Subreddit>(new Thing { Kind = "t5", Data = new Subreddit { Headertitle = name, Title = name, Url = string.Format("r/{0}", name), Created = DateTime.Now, CreatedUTC = DateTime.UtcNow, DisplayName = name, Description = "there doesnt seem to be anything here", Name = name, Over18 = false, PublicDescription = "there doesnt seem to be anything here", Subscribers = 0 } });
  220. }
  221. else
  222. {
  223. var labeledMulti = new TypedThing<LabeledMulti>(JsonConvert.DeserializeObject<Thing>(comments));
  224. var multiPath = labeledMulti.Data.Path;
  225. if(!string.IsNullOrWhiteSpace(currentUser.Username))
  226. multiPath = multiPath.Replace("/user/" + currentUser.Username, "/me");
  227. return new TypedThing<Subreddit>(new Thing { Kind = "t5", Data = new Subreddit { DisplayName = labeledMulti.Data.Name, Title = labeledMulti.Data.Name, Url = multiPath, Headertitle = labeledMulti.Data.Name, Over18 = false } });
  228. }
  229. }
  230. catch (Exception ex)
  231. {
  232. //_notificationService.CreateErrorNotification(ex);
  233. return new TypedThing<Subreddit>(new Thing { Kind = "t5", Data = new Subreddit { Headertitle = name, Title = name, Url = string.Format("r/{0}", name), Created = DateTime.Now, CreatedUTC = DateTime.UtcNow, DisplayName = name, Description = "there doesnt seem to be anything here", Name = name, Over18 = false, PublicDescription = "there doesnt seem to be anything here", Subscribers = 0 } });
  234. }
  235. }
  236. }
  237. public async Task<Listing> GetPostsByUser(string username, int? limit)
  238. {
  239. var maxLimit = (await UserIsGold()) ? 1500 : 100;
  240. var guardedLimit = Math.Min(maxLimit, limit ?? maxLimit);
  241. var targetUri = string.Format("http://www.reddit.com/user/{0}/.json?limit={1}", username, guardedLimit);
  242. try
  243. {
  244. var comments = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  245. var newListing = JsonConvert.DeserializeObject<Listing>(comments);
  246. return MaybeFilterForNSFW(newListing);
  247. }
  248. catch (Exception ex)
  249. {
  250. _notificationService.CreateErrorNotification(ex);
  251. return new Listing { Kind = "Listing", Data = new ListingData { Children = new List<Thing>() } };
  252. }
  253. }
  254. public async Task<Listing> GetPostsBySubreddit(string subreddit, int? limit)
  255. {
  256. var maxLimit = (await UserIsGold()) ? 1500 : 100;
  257. var guardedLimit = Math.Min(maxLimit, limit ?? maxLimit);
  258. if (subreddit == null)
  259. {
  260. //this isnt the front page, that would be "/"
  261. //return empty since there isnt anything here
  262. _notificationService.CreateNotification("There doesnt seem to be anything here");
  263. return new Listing { Kind = "Listing", Data = new ListingData { Children = new List<Thing>() } };
  264. }
  265. var targetUri = string.Format("http://www.reddit.com{0}.json?limit={1}", subreddit, guardedLimit);
  266. try
  267. {
  268. var links = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  269. var newListing = JsonConvert.DeserializeObject<Listing>(links);
  270. return MaybeInjectAdvertisements(MaybeFilterForNSFW(newListing));
  271. }
  272. catch (Exception ex)
  273. {
  274. _notificationService.CreateErrorNotification(ex);
  275. return new Listing { Kind = "Listing", Data = new ListingData { Children = new List<Thing>() } };
  276. }
  277. }
  278. public async Task<Listing> GetMoreOnListing(IEnumerable<string> childrenIds, string contentId, string subreddit)
  279. {
  280. var targetUri = "http://www.reddit.com/api/morechildren.json";
  281. if (childrenIds.Count() == 0)
  282. return new Listing
  283. {
  284. Kind = "Listing",
  285. Data = new ListingData()
  286. };
  287. var arguments = new Dictionary<string, string>
  288. {
  289. {"children", string.Join(",", childrenIds) },
  290. {"link_id", contentId },
  291. {"pv_hex", ""},
  292. {"api_type", "json" }
  293. };
  294. if (subreddit != null)
  295. {
  296. arguments.Add("r", subreddit);
  297. }
  298. try
  299. {
  300. var result = await _simpleHttpService.SendPost(await GetCurrentLoginCookie(), arguments, targetUri);
  301. var newListing = new Listing
  302. {
  303. Kind = "Listing",
  304. Data = new ListingData { Children = JsonConvert.DeserializeObject<JsonThing>(result).Json.Data.Things }
  305. };
  306. return MaybeInjectAdvertisements(MaybeFilterForNSFW(newListing));
  307. }
  308. catch (Exception ex)
  309. {
  310. _notificationService.CreateErrorNotification(ex);
  311. return new Listing
  312. {
  313. Kind = "Listing",
  314. Data = new ListingData { Children = new List<Thing>() }
  315. };
  316. }
  317. }
  318. Tuple<DateTime, string, string, Listing> _lastCommentsOnPostRequest;
  319. public async Task<Thing> GetLinkByUrl(string url)
  320. {
  321. try
  322. {
  323. var originalUrl = url;
  324. if (originalUrl.Contains(".json"))
  325. {
  326. }
  327. else if (originalUrl.Contains("?"))
  328. {
  329. var queryPos = url.IndexOf("?");
  330. url = string.Format("{0}.json{1}", originalUrl.Remove(queryPos), originalUrl.Substring(queryPos));
  331. }
  332. else
  333. {
  334. url = originalUrl + ".json";
  335. }
  336. Listing listing = null;
  337. var comments = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), url);
  338. if (comments.StartsWith("["))
  339. {
  340. var listings = JsonConvert.DeserializeObject<Listing[]>(comments);
  341. listing = new Listing { Data = new ListingData { Children = new List<Thing>() } };
  342. foreach (var combinableListing in listings)
  343. {
  344. listing.Data.Children.AddRange(combinableListing.Data.Children);
  345. listing.Kind = combinableListing.Kind;
  346. listing.Data.After = combinableListing.Data.After;
  347. listing.Data.Before = combinableListing.Data.Before;
  348. }
  349. }
  350. else
  351. listing = JsonConvert.DeserializeObject<Listing>(comments);
  352. var requestedLinkInfo = listing.Data.Children.FirstOrDefault(thing => thing.Data is Link);
  353. if (requestedLinkInfo != null)
  354. {
  355. var result = MaybeFilterForNSFW(listing);
  356. ((Link)requestedLinkInfo.Data).Permalink = originalUrl;
  357. _lastCommentsOnPostRequest = Tuple.Create(DateTime.Now, ((Link)requestedLinkInfo.Data).Subreddit, ((Link)requestedLinkInfo.Data).Permalink, result);
  358. return requestedLinkInfo;
  359. }
  360. else
  361. return null;
  362. }
  363. catch(Exception ex)
  364. {
  365. _notificationService.CreateErrorNotification(ex);
  366. return null;
  367. }
  368. }
  369. public async Task<Listing> GetCommentsOnPost(string subreddit, string permalink, int? limit)
  370. {
  371. //comments are pretty slow to get, so cache it to within 5 minutes for the most recent request
  372. if (_lastCommentsOnPostRequest != null &&
  373. (DateTime.Now - _lastCommentsOnPostRequest.Item1).TotalMinutes < 5 &&
  374. _lastCommentsOnPostRequest.Item2 == subreddit &&
  375. _lastCommentsOnPostRequest.Item3 == permalink)
  376. return _lastCommentsOnPostRequest.Item4;
  377. try
  378. {
  379. var maxLimit = (await UserIsGold()) ? 1500 : 500;
  380. var guardedLimit = Math.Min(maxLimit, limit ?? maxLimit);
  381. string targetUri = null;
  382. if (permalink.Contains("reddit.com"))
  383. {
  384. permalink = permalink.Substring(permalink.IndexOf("reddit.com") + "reddit.com".Length);
  385. }
  386. if (permalink.Contains(".json?"))
  387. {
  388. targetUri = "http://www.reddit.com" + permalink;
  389. }
  390. else if (permalink.Contains("?"))
  391. {
  392. var queryPos = permalink.IndexOf("?");
  393. targetUri = string.Format("http://www.reddit.com{0}.json{1}", permalink.Remove(queryPos), permalink.Substring(queryPos));
  394. }
  395. else
  396. {
  397. targetUri = limit == -1 ?
  398. string.Format("http://www.reddit.com{0}.json", permalink) :
  399. string.Format("http://www.reddit.com{0}.json?limit={1}", permalink, limit);
  400. }
  401. Listing listing = null;
  402. var comments = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  403. if (comments.StartsWith("["))
  404. {
  405. var listings = JsonConvert.DeserializeObject<Listing[]>(comments);
  406. listing = new Listing { Data = new ListingData { Children = new List<Thing>() } };
  407. foreach (var combinableListing in listings)
  408. {
  409. listing.Data.Children.AddRange(combinableListing.Data.Children);
  410. listing.Kind = combinableListing.Kind;
  411. listing.Data.After = combinableListing.Data.After;
  412. listing.Data.Before = combinableListing.Data.Before;
  413. }
  414. }
  415. else
  416. listing = JsonConvert.DeserializeObject<Listing>(comments);
  417. var result = MaybeFilterForNSFW(listing);
  418. var requestedLinkInfo = listing.Data.Children.FirstOrDefault(thing => thing.Data is Link);
  419. if (requestedLinkInfo != null)
  420. {
  421. if (!_linkToOpMap.ContainsKey(((Link)requestedLinkInfo.Data).Name))
  422. {
  423. _linkToOpMap.Add(((Link)requestedLinkInfo.Data).Name, ((Link)requestedLinkInfo.Data).Author);
  424. }
  425. }
  426. _lastCommentsOnPostRequest = Tuple.Create(DateTime.Now, subreddit, permalink, result);
  427. return result;
  428. }
  429. catch (Exception ex)
  430. {
  431. _notificationService.CreateErrorNotification(ex);
  432. return new Listing { Kind = "Listing", Data = new ListingData { Children = new List<Thing>() } };
  433. }
  434. }
  435. public Task<Listing> GetMessages(int? limit)
  436. {
  437. return GetMail("messages", limit);
  438. }
  439. public void AddFlairInfo(string linkId, string opName)
  440. {
  441. if (!_linkToOpMap.ContainsKey(linkId))
  442. {
  443. _linkToOpMap.Add(linkId, opName);
  444. }
  445. }
  446. public async Task<Listing> GetAdditionalFromListing(string baseUrl, string after, int? limit)
  447. {
  448. var maxLimit = (await UserIsGold()) ? 1500 : 500;
  449. var guardedLimit = Math.Min(maxLimit, limit ?? maxLimit);
  450. string targetUri = null;
  451. //if this base url already has arguments (like search) just append the count and the after
  452. if (baseUrl.Contains(".json?"))
  453. targetUri = string.Format("{0}&limit={1}&after={2}", baseUrl, guardedLimit, after);
  454. else
  455. targetUri = string.Format("{0}.json?limit={1}&after={2}", baseUrl, guardedLimit, after);
  456. try
  457. {
  458. var listing = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  459. var newListing = JsonConvert.DeserializeObject<Listing>(listing);
  460. return MaybeInjectAdvertisements(MaybeFilterForNSFW(newListing));
  461. }
  462. catch (Exception ex)
  463. {
  464. _notificationService.CreateErrorNotification(ex);
  465. return new Listing { Kind = "Listing", Data = new ListingData { Children = new List<Thing>() } };
  466. }
  467. }
  468. public async Task<TypedThing<Account>> GetAccountInfo(string accountName)
  469. {
  470. var targetUri = string.Format("http://www.reddit.com/user/{0}/about.json", accountName);
  471. try
  472. {
  473. var account = await _simpleHttpService.UnAuthedGet(targetUri);
  474. return new TypedThing<Account>(JsonConvert.DeserializeObject<Thing>(account));
  475. }
  476. catch (Exception ex)
  477. {
  478. _notificationService.CreateErrorNotification(ex);
  479. return new TypedThing<Account>(new Thing { Kind = "t3", Data = new Account { Name = accountName } });
  480. }
  481. }
  482. private void ProcessJsonErrors(string response)
  483. {
  484. string realErrorString = "";
  485. try
  486. {
  487. if (response.Contains("errors"))
  488. {
  489. var jsonErrors = JsonConvert.DeserializeObject<JsonErrorsData>(response);
  490. if (jsonErrors.Errors != null && jsonErrors.Errors.Length > 0)
  491. {
  492. realErrorString = jsonErrors.Errors[0].ToString();
  493. }
  494. }
  495. }
  496. catch
  497. {
  498. }
  499. if (!string.IsNullOrWhiteSpace(realErrorString))
  500. throw new Exception(realErrorString);
  501. }
  502. public virtual async Task AddVote(string thingId, int direction)
  503. {
  504. var modhash = await GetCurrentModhash();
  505. var arguments = new Dictionary<string, string>
  506. {
  507. {"id", thingId},
  508. {"dir", direction.ToString()},
  509. {"uh", modhash}
  510. };
  511. ProcessJsonErrors(await _simpleHttpService.SendPost(await GetCurrentLoginCookie(), arguments, "http://www.reddit.com/api/vote"));
  512. }
  513. public virtual async Task AddSubredditSubscription(string subreddit, bool unsub)
  514. {
  515. var modhash = await GetCurrentModhash();
  516. var content = new Dictionary<string, string>
  517. {
  518. { "sr", subreddit},
  519. { "uh", modhash},
  520. { "action", unsub ? "unsub" : "sub"}
  521. };
  522. await _simpleHttpService.SendPost(await GetCurrentLoginCookie(), content, "http://www.reddit.com/api/subscribe");
  523. }
  524. public virtual Task AddSavedThing(string thingId)
  525. {
  526. return ThingAction("save", thingId);
  527. }
  528. public virtual Task AddReportOnThing(string thingId)
  529. {
  530. return ThingAction("report", thingId);
  531. }
  532. public virtual async Task AddPost(string kind, string url, string text, string subreddit, string title)
  533. {
  534. var modhash = await GetCurrentModhash();
  535. var arguments = new Dictionary<string, string>
  536. {
  537. {"api_type", "json"},
  538. {"kind", kind},
  539. {"url", url},
  540. {"text", text},
  541. {"title", title},
  542. {"sr", subreddit},
  543. {"renderstyle", "html" },
  544. {"uh", modhash}
  545. };
  546. ProcessJsonErrors(await this.SendPost(await GetCurrentLoginCookie(), arguments, "http://www.reddit.com/api/submit"));
  547. }
  548. public virtual async Task EditPost(string text, string name)
  549. {
  550. var modhash = await GetCurrentModhash();
  551. var arguments = new Dictionary<string, string>
  552. {
  553. {"api_type", "json"},
  554. {"text", text},
  555. {"thing_id", name},
  556. {"uh", modhash}
  557. };
  558. ProcessJsonErrors(await this.SendPost(await GetCurrentLoginCookie(), arguments, "http://www.reddit.com/api/editusertext"));
  559. }
  560. public async Task SubmitCaptcha(string captcha)
  561. {
  562. Captcha = captcha;
  563. List<PostData> data = PostQueue.ToList<PostData>();
  564. PostQueue.Clear();
  565. foreach (var post in data)
  566. {
  567. await this.SendPost(post.Cookie, post.UrlEncodedData, post.Uri, true);
  568. }
  569. }
  570. private class PostData
  571. {
  572. public string Cookie { get; set; }
  573. public Dictionary<string, string> UrlEncodedData { get; set; }
  574. public string Uri { get; set; }
  575. }
  576. private List<PostData> PostQueue = new List<PostData>();
  577. private string CaptchaIden { get; set; }
  578. private string Captcha { get; set; }
  579. private async Task<string> SendPost(string cookie, Dictionary<string, string> urlEncodedData, string uri, bool queuedMessage = false)
  580. {
  581. if (!urlEncodedData.ContainsKey("api_type"))
  582. urlEncodedData.Add("api_type", "json");
  583. if (!String.IsNullOrEmpty(CaptchaIden))
  584. {
  585. if (urlEncodedData.ContainsKey("iden"))
  586. urlEncodedData["iden"] = CaptchaIden;
  587. else
  588. urlEncodedData.Add("iden", CaptchaIden);
  589. }
  590. if (!String.IsNullOrEmpty(Captcha))
  591. {
  592. if (urlEncodedData.ContainsKey("captcha"))
  593. urlEncodedData["captcha"] = Captcha;
  594. else
  595. urlEncodedData.Add("captcha", Captcha);
  596. }
  597. string response = null;
  598. response = await _simpleHttpService.SendPost(cookie, urlEncodedData, uri);
  599. var jsonObject = JsonConvert.DeserializeObject(response) as JObject;
  600. JToken captcha = null;
  601. JToken errors = null;
  602. JObject first = null;
  603. if (jsonObject.First != null)
  604. first = (jsonObject.First as JProperty).Value as JObject;
  605. if (first != null)
  606. {
  607. first.TryGetValue("captcha", out captcha);
  608. first.TryGetValue("errors", out errors);
  609. if (captcha != null)
  610. CaptchaIden = captcha.Value<string>();
  611. if (captcha != null && errors != null)
  612. {
  613. var user = await _userService.GetUser();
  614. user.NeedsCaptcha = true;
  615. // If a user has told us to bug off this session, do as they say
  616. if (!_settingsService.PromptForCaptcha)
  617. return response;
  618. PostQueue.Add(new PostData { Cookie = cookie, Uri = uri, UrlEncodedData = urlEncodedData });
  619. CaptchaViewModel captchaVM = CaptchaViewModel.GetInstance(_baconProvider);
  620. captchaVM.ShowCaptcha(CaptchaIden);
  621. }
  622. }
  623. return response;
  624. }
  625. public virtual async Task ReadMessage(string id)
  626. {
  627. var modhash = await GetCurrentModhash();
  628. var arguments = new Dictionary<string, string>
  629. {
  630. {"id", id},
  631. {"uh", modhash}
  632. };
  633. ProcessJsonErrors(await this.SendPost(await GetCurrentLoginCookie(), arguments, "http://www.reddit.com/api/read_message"));
  634. }
  635. public virtual async Task AddMessage(string recipient, string subject, string message)
  636. {
  637. var modhash = await GetCurrentModhash();
  638. var arguments = new Dictionary<string, string>
  639. {
  640. {"id", "#compose-message"},
  641. {"to", recipient},
  642. {"text", message},
  643. {"subject", subject},
  644. {"thing-id", ""},
  645. {"renderstyle", "html"},
  646. {"uh", modhash}
  647. };
  648. ProcessJsonErrors(await this.SendPost(await GetCurrentLoginCookie(), arguments, "http://www.reddit.com/api/compose"));
  649. }
  650. public virtual async Task AddReply(string recipient, string subject, string message, string thing_id)
  651. {
  652. var modhash = await GetCurrentModhash();
  653. var arguments = new Dictionary<string, string>
  654. {
  655. {"id", "#compose-message"},
  656. {"to", recipient},
  657. {"text", message},
  658. {"subject", subject},
  659. {"thing-id", ""},
  660. {"renderstyle", "html"},
  661. {"uh", modhash}
  662. };
  663. ProcessJsonErrors(await this.SendPost(await GetCurrentLoginCookie(), arguments, "http://www.reddit.com/api/compose"));
  664. }
  665. public virtual async Task AddComment(string parentId, string content)
  666. {
  667. var modhash = await GetCurrentModhash();
  668. var arguments = new Dictionary<string, string>
  669. {
  670. {"thing_id", parentId},
  671. {"text", content.Replace("\r\n", "\n")},
  672. {"uh", modhash}
  673. };
  674. ProcessJsonErrors(await this.SendPost(await GetCurrentLoginCookie(), arguments, "http://www.reddit.com/api/comment"));
  675. }
  676. public virtual async Task EditComment(string thingId, string text)
  677. {
  678. var modhash = await GetCurrentModhash();
  679. var arguments = new Dictionary<string, string>
  680. {
  681. {"thing_id", thingId},
  682. {"text", text.Replace("\r\n", "\n")},
  683. {"uh", modhash}
  684. };
  685. ProcessJsonErrors(await this.SendPost(await GetCurrentLoginCookie(), arguments, "http://www.reddit.com/api/editusertext"));
  686. }
  687. private async Task<bool> UserIsGold()
  688. {
  689. var user = await _userService.GetUser();
  690. return user != null && user.Me != null && user.Me.IsGold;
  691. }
  692. private async Task<string> GetCurrentLoginCookie()
  693. {
  694. var currentUser = await _userService.GetUser();
  695. if (currentUser != null && !string.IsNullOrWhiteSpace(currentUser.LoginCookie))
  696. {
  697. return currentUser.LoginCookie;
  698. }
  699. else
  700. return string.Empty;
  701. }
  702. private async Task<string> GetCurrentModhash()
  703. {
  704. var currentUser = await _userService.GetUser();
  705. if (currentUser != null && !string.IsNullOrWhiteSpace(currentUser.LoginCookie) && currentUser.Me != null)
  706. {
  707. return currentUser.Me.ModHash;
  708. }
  709. else if (currentUser != null && !string.IsNullOrWhiteSpace(currentUser.LoginCookie))
  710. {
  711. currentUser.Me = await GetMe();
  712. return currentUser.Me.ModHash;
  713. }
  714. else
  715. return string.Empty;
  716. }
  717. private Listing MaybeFilterForNSFW(Listing source)
  718. {
  719. if (_settingsService.AllowOver18)
  720. {
  721. return source;
  722. }
  723. else
  724. return FilterForNSFW(source);
  725. }
  726. private Listing MaybeInjectAdvertisements(Listing source)
  727. {
  728. if (!_settingsService.AllowAdvertising)
  729. return source;
  730. int count = source.Data.Children.Count;
  731. for (int i = 9; i < count; i += 10)
  732. {
  733. var thing = new Thing { Data = new Advertisement(), Kind = "ad" };
  734. source.Data.Children.Insert(i, thing);
  735. }
  736. return source;
  737. }
  738. private Listing FilterForNSFW(Listing source)
  739. {
  740. source.Data.Children = source.Data.Children
  741. .Select(FilterForNSFW)
  742. .Where(thing => thing != null)
  743. .ToList();
  744. return source;
  745. }
  746. private Thing FilterForNSFW(Thing source)
  747. {
  748. if (source.Data is Link || source.Data is Subreddit)
  749. {
  750. if (((dynamic)source.Data).Over18)
  751. return null;
  752. }
  753. return source;
  754. }
  755. public AuthorFlairKind GetUsernameModifiers(string username, string linkid, string subreddit)
  756. {
  757. if (!string.IsNullOrEmpty(linkid))
  758. {
  759. string opName;
  760. if (_linkToOpMap.TryGetValue(linkid, out opName) && opName == username)
  761. {
  762. return AuthorFlairKind.OriginalPoster;
  763. }
  764. }
  765. if (!string.IsNullOrEmpty(subreddit))
  766. {
  767. HashSet<string> subredditMods;
  768. if (_subredditToModMap.TryGetValue(subreddit, out subredditMods) && subredditMods != null && subredditMods.Contains(username))
  769. {
  770. return AuthorFlairKind.Moderator;
  771. }
  772. }
  773. return AuthorFlairKind.None;
  774. }
  775. private async Task<Listing> GetUserMultis(Listing listing)
  776. {
  777. var targetUri = string.Format("http://www.reddit.com/api/multi/mine.json");
  778. try
  779. {
  780. var subreddits = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  781. if (subreddits == "[]")
  782. return listing;
  783. else
  784. {
  785. var currentUser = await _userService.GetUser();
  786. var userMultis = JsonConvert.DeserializeObject<Thing[]>(subreddits);
  787. foreach (var thing in userMultis)
  788. {
  789. var labeledMulti = new TypedThing<LabeledMulti>(thing);
  790. var multiPath = labeledMulti.Data.Path;
  791. multiPath = multiPath.Replace("/user/" + currentUser.Username, "/me");
  792. listing.Data.Children.Insert(0, (new Thing { Kind = "t5", Data = new Subreddit { DisplayName = labeledMulti.Data.Name, HeaderImage = "/Assets/multireddit.png", Title = labeledMulti.Data.Name, Url = multiPath, Headertitle = labeledMulti.Data.Name, Over18 = false } }));
  793. }
  794. }
  795. }
  796. //this api is most likely still in flux just silently fail if they break us down the line
  797. catch {}
  798. return listing;
  799. }
  800. public async Task<Listing> GetSubscribedSubredditListing()
  801. {
  802. var maxLimit = (await UserIsGold()) ? 1500 : 100;
  803. var targetUri = string.Format("http://www.reddit.com/reddits/mine.json?limit={0}", maxLimit);
  804. try
  805. {
  806. var subreddits = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  807. if (subreddits == "\"{}\"")
  808. return await GetDefaultSubreddits();
  809. else
  810. return await GetUserMultis(JsonConvert.DeserializeObject<Listing>(subreddits));
  811. }
  812. catch (Exception ex)
  813. {
  814. _notificationService.CreateErrorNotification(ex);
  815. }
  816. //cant await in a catch block so do it after
  817. return await GetDefaultSubreddits();
  818. }
  819. public async Task<Listing> GetDefaultSubreddits()
  820. {
  821. return JsonConvert.DeserializeObject<Listing>(Resources.DefaultSubreddits1 + Resources.DefaultSubreddits2 + Resources.DefaultSubreddits3);
  822. }
  823. public async Task<bool> CheckLogin(string loginToken)
  824. {
  825. var meString = await _simpleHttpService.SendGet(loginToken, "http://www.reddit.com/api/me.json");
  826. return (!string.IsNullOrWhiteSpace(meString) && meString != "{}");
  827. }
  828. public async Task<Listing> GetSaved(int? limit)
  829. {
  830. return await GetUserInfoListing("saved", limit);
  831. }
  832. public async Task<Listing> GetLiked(int? limit)
  833. {
  834. return await GetUserInfoListing("liked", limit);
  835. }
  836. private async Task<Listing> GetUserInfoListing(string kind, int? limit)
  837. {
  838. var maxLimit = (await UserIsGold()) ? 1500 : 100;
  839. var guardedLimit = Math.Min(maxLimit, limit ?? maxLimit);
  840. var targetUri = string.Format("http://www.reddit.com/user/{0}/{2}/.json?limit={1}", (await _userService.GetUser()).Username, guardedLimit, kind);
  841. try
  842. {
  843. var info = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  844. var newListing = JsonConvert.DeserializeObject<Listing>(info);
  845. return MaybeFilterForNSFW(newListing);
  846. }
  847. catch (Exception ex)
  848. {
  849. _notificationService.CreateErrorNotification(ex);
  850. return new Listing { Kind = "Listing", Data = new ListingData { Children = new List<Thing>() } };
  851. }
  852. }
  853. public async Task<Listing> GetDisliked(int? limit)
  854. {
  855. return await GetUserInfoListing("disliked", limit);
  856. }
  857. public Task<Listing> GetSentMessages(int? limit)
  858. {
  859. return GetMail("sent", limit);
  860. }
  861. public async Task ThingAction(string action, string thingId)
  862. {
  863. var modhash = await GetCurrentModhash();
  864. var targetUri = "http://www.reddit.com/api/" + action;
  865. var content = new Dictionary<string, string>
  866. {
  867. { "id", thingId},
  868. { "uh", modhash}
  869. };
  870. ProcessJsonErrors(await _simpleHttpService.SendPost(await GetCurrentLoginCookie(), content, targetUri));
  871. }
  872. public Task UnSaveThing(string thingId)
  873. {
  874. return ThingAction("unsafe", thingId);
  875. }
  876. public async Task MarkVisited(IEnumerable<string> ids)
  877. {
  878. var user = await _userService.GetUser();
  879. if (user != null && user.Me != null && user.Me.IsGold)
  880. {
  881. var modhash = await GetCurrentModhash();
  882. var arguments = new Dictionary<string, string>
  883. {
  884. {"links", string.Join(",", ids)},
  885. { "uh", modhash}
  886. };
  887. ProcessJsonErrors(await this.SendPost(await GetCurrentLoginCookie(), arguments, "http://www.reddit.com/api/store_visits"));
  888. }
  889. }
  890. public async Task<Listing> GetModActions(string subreddit, int? limit)
  891. {
  892. var maxLimit = (await UserIsGold()) ? 1500 : 100;
  893. var guardedLimit = Math.Min(maxLimit, limit ?? maxLimit);
  894. var targetUri = string.Format("http://www.reddit.com/r/{0}/about/log.json?limi={1}", subreddit, guardedLimit);
  895. try
  896. {
  897. var messages = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  898. if (messages == "\"{}\"")
  899. {
  900. return new Listing { Kind = "Listing", Data = new ListingData { Children = new List<Thing>() } };
  901. }
  902. return JsonConvert.DeserializeObject<Listing>(messages);
  903. }
  904. catch (Exception ex)
  905. {
  906. _notificationService.CreateErrorNotification(ex);
  907. return new Listing { Kind = "Listing", Data = new ListingData { Children = new List<Thing>() } };
  908. }
  909. }
  910. private async Task<Listing> GetMail(string kind, int? limit)
  911. {
  912. var maxLimit = (await UserIsGold()) ? 1500 : 100;
  913. var guardedLimit = Math.Min(maxLimit, limit ?? maxLimit);
  914. var targetUri = string.Format("http://www.reddit.com/message/{1}/.json?limit={0}", guardedLimit, kind);
  915. try
  916. {
  917. var messages = await _simpleHttpService.SendGet(await GetCurrentLoginCookie(), targetUri);
  918. if (messages == "\"{}\"")
  919. {
  920. return new Listing { Kind = "Listing", Data = new ListingData { Children = new List<Thing>() } };
  921. }
  922. // Hacky hack mcHackerson
  923. messages = messages.Replace("\"kind\": \"t1\"", "\"kind\": \"t4.5\"");
  924. return JsonConvert.DeserializeObject<Listing>(messages);
  925. }
  926. catch (Exception ex)
  927. {
  928. _notificationService.CreateErrorNotification(ex);
  929. return new Listing { Kind = "Listing", Data = new ListingData { Children = new List<Thing>() } };
  930. }
  931. }
  932. public Task<Listing> GetModMail(int? limit)
  933. {
  934. return GetMail("moderator", limit);
  935. }
  936. public Task ApproveThing(string thingId)
  937. {
  938. return ThingAction("approve", thingId);
  939. }
  940. public async Task RemoveThing(string thingId, bool spam)
  941. {
  942. var modhash = await GetCurrentModhash();
  943. var targetUri = "http://www.reddit.com/api/remove";
  944. var content = new Dictionary<string, string>
  945. {
  946. { "id", thingId},
  947. { "uh", modhash},
  948. { "spam", spam ? "true" : "false"}
  949. };
  950. ProcessJsonErrors(await _simpleHttpService.SendPost(await GetCurrentLoginCookie(), content, targetUri));
  951. }
  952. public Task IgnoreReportsOnThing(string thingId)
  953. {
  954. return ThingAction("ignore_reports", thingId);
  955. }
  956. public async Task Friend(string name, string container, string note, string type)
  957. {
  958. var modhash = await GetCurrentModhash();
  959. var targetUri = "http://www.reddit.com/api/friend";
  960. var content = new Dictionary<string, string>
  961. {
  962. { "api_type", "json"},
  963. { "uh", modhash},
  964. { "container", container },
  965. { "name", name },
  966. { "note", note},
  967. { "type", type }
  968. };
  969. ProcessJsonErrors(await _simpleHttpService.SendPost(await GetCurrentLoginCookie(), content, targetUri));
  970. }
  971. public async Task Unfriend(string name, string container, string type)
  972. {
  973. var modhash = await GetCurrentModhash();
  974. var targetUri = "http://www.reddit.com/api/unfriend";
  975. var content = new Dictionary<string, string>
  976. {
  977. { "uh", modhash},
  978. { "container", container },
  979. { "name", name },
  980. { "type", type }
  981. };
  982. ProcessJsonErrors(await _simpleHttpService.SendPost(await GetCurrentLoginCookie(), content, targetUri));
  983. }
  984. public Task AddContributor(string name, string subreddit, string note)
  985. {
  986. return Friend(name, subreddit, note, "contributor");
  987. }
  988. public Task RemoveContributor(string subreddit, string name)
  989. {
  990. return Unfriend(name, subreddit, "contributor");
  991. }
  992. public Task AddModerator(string name, string subreddit, string note)
  993. {
  994. return Friend(name, subreddit, note, "moderator");
  995. }
  996. public Task RemoveModerator(string subreddit, string name)
  997. {
  998. return Unfriend(name, subreddit, "moderator");
  999. }
  1000. public Task AddBan(string name, string subreddit, string note)
  1001. {
  1002. return Friend(name, subreddit, note, "banned");
  1003. }
  1004. public Task RemoveBan(string subreddit, string name)
  1005. {
  1006. return Unfriend(name, subreddit, "banned");
  1007. }
  1008. }
  1009. }