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