public void Update() { try { List <Contact> OldContacts = new List <Contact>(Contacts); HttpResult ret = http.Get("https://www.google.com/voice?ui=desktop"); var m = Regex.Match(ret.Page, @"_gcData.*?\=(.*?)_gvRun", RegexOptions.Singleline); if (m.Success) { // hack: :( var json = m.Groups[1].Value.Replace('\'', '"'); json = Regex.Replace(json, "\"flags\":\\s*{\\s*};", "", RegexOptions.Singleline); var o = JObject.Parse(json); var contacts = o["contacts"]; string BasePhotoUrl = "http://www.google.com/s2/b/0"; // (Body["UserData"]["PhotoUrl"] as JValue).Value.ToString(); Trace.WriteLine("Base Photo URL: " + BasePhotoUrl); // We'll fail on any bad contact, because we want to FailFast here // that way, the user will not get any contacts, and hopefully report // the bug to us. foreach (var cx in contacts) { var contact = cx.First; Trace.WriteLine(contact); string ID = (contact["contactId"] as JValue).Value.ToString(); string Name = (contact["name"] as JValue).Value.ToString(); bool shouldAdd = false; Contact c = Contacts.SingleOrDefault(x => x.ID == ID); if (c == null) { c = new Contact(); shouldAdd = true; } else { OldContacts.Remove(c); } c.ID = ID; c.Name = WebUtil.HtmlDecode(Name); if (c.Name.Contains("Microsoft")) { // Debugger.Break(); } c.Phones.Clear(); // kill old phones. foreach (var ph in (JArray)contact["numbers"]) { try { if (ph.ToString().Contains("phoneType")) { c.Phones.Add(new Phone { Number = (ph["phoneNumber"] as JValue).Value.ToString(), Type = (ph["phoneType"] as JValue).Value.ToString() }); } else { // NOTE: 5/5/2012 // Contacts with 'Custom' label don't have a phoneType c.Phones.Add(new Phone { Number = (ph["phoneNumber"] as JValue).Value.ToString(), Type = "Unknown", }); } } catch (NullReferenceException) { Debugger.Break(); // no phoneType = GV number we should ignore } } if (LoadImages) { try { string photoUrl = (contact["photoUrl"] as JValue).Value.ToString(); var r_ImgID = Regex.Match(photoUrl, ".*/(.*?)$", RegexOptions.Singleline); string ImgID = ""; if (!r_ImgID.Success) { ImgID = Util.SafeFileName(photoUrl); } else { ImgID = Util.SafeFileName(r_ImgID.Groups[1].Value); } if (!string.IsNullOrEmpty(ImgID)) { string save = ImageDir + ImgID + ".jpg"; try { if (!File.Exists(save)) { var imageBytes = http.GetFile(BasePhotoUrl + photoUrl); Trace.WriteLine("Saving: " + save); File.WriteAllBytes(save, imageBytes); } } catch (Exception ex) { // if we fail to save, we don't want to attempt it every time the contacts sync // so we'll just save anyway. // TODO consider failed=true property on contact download Trace.WriteLine("GoogleVoice/ContactsManager/Update/Photo Save Error: " + ex.Message); } c.ImageLocation = save; c.ImageETag = photoUrl; } } catch (Exception ex) { Trace.WriteLine("Photo: " + ex.Message); } } if (shouldAdd) { Contacts.Add(c); } } foreach (Contact deletedContact in OldContacts) { Trace.WriteLine("Removing orphaned contact: " + deletedContact); Contacts.Remove(deletedContact); } Contacts.Sort(new ContactComparer()); if (OnContactsUpdated != null) { OnContactsUpdated(); } } } catch (Exception ex) { Trace.WriteLine("Error updating contacts: " + ex); } }
public void Login() { // fiddler2 // GlobalProxySelection.Select = new WebProxy("127.0.0.1", 8888); HttpResult ret = null; Trace.WriteLine("GoogleVoice/Account/Login UserName: "******"Connecting..."); ret = http.Get("https://www.google.com/voice/m"); if (ret.Uri.LocalPath == "/ServiceLogin") { string Token_GALX = http.GetCookieString("GALX", "https://accounts.google.com/"); if (string.IsNullOrEmpty(Token_GALX)) { // I'm pretty confident that we can't login without a GALX. throw new GVLoginException("GALX missing"); } OnLoginMessage_Internal("Logging in..."); ret = http.Post("https://accounts.google.com/ServiceLoginAuth?service=grandcentral", new Dictionary <string, string> { { "ltmpl", "mobile" }, // Probably don't need one of these { "btmpl", "mobile" }, { "followup", "https://www.google.com/voice/m?initialauth" }, { "continue", "https://www.google.com/voice/m?initialauth" }, { "service", "grandcentral" }, { "bgresponse", "js_disabled" }, { "PersistentCookie", "yes" }, { "GALX", Token_GALX }, { "Email", UserName }, { "Passwd", Password }, }); if (ret.Uri.ToString() == "https://www.google.com/voice/m") { // we're logged in! Token_GVX = http.GetCookieString("gvx", "https://www.google.com/voice/m"); if (string.IsNullOrEmpty(Token_GVX)) { throw new GVLoginException("GVX is missing after primary redirect"); } } else if (ret.Uri.ToString().StartsWith("https://accounts.google.com/SecondFactor")) { // 2-step verification - SMS verification required. Match sms = Regex.Match(ret.Page, "name=\"secTok\".*?value='(.*?)'", RegexOptions.Singleline); if (sms.Success) { string Timestamp = ""; Match timeStmp = Regex.Match(ret.Page, "name=\"timeStmp\".*?value='(.*?)'", RegexOptions.Singleline); if (timeStmp.Success) { Timestamp = timeStmp.Groups[1].Value; } string secTok = sms.Groups[1].Value; string Token_UserSMSPin = GetSMSPinFromUser(); Trace.WriteLine("secTok: " + secTok); Trace.WriteLine("User-Provided PIN: " + Token_UserSMSPin); ret = http.Post("https://accounts.google.com/SecondFactor", new Dictionary <string, string> { { "continue", "https://www.google.com/voice/m?initialauth" }, { "service", "grandcentral" }, { "smsToken", "" }, { "secTok", secTok }, { "timeStmp", Timestamp }, { "smsUserPin", Token_UserSMSPin }, { "PersistentCookie", "true" }, { "smsVerifyPin", "Verify" }, { "PersistentOptionSelection", "1" }, }); Cookie Token_SMSV = http.GetCookie("SMSV", "https://accounts.google.com/"); SMSVCookieUpdated_Internal(new GVCookie(Token_SMSV)); if (ret.Uri.ToString() == "https://www.google.com/voice/m") { // Logged in, find the cookie. Token_GVX = http.GetCookieString("gvx", "https://www.google.com/voice/m"); if (string.IsNullOrEmpty(Token_GVX)) { throw new GVLoginException("GVX is missing after validation response"); } } else { Trace.WriteLine("Didn't get expected redirect: " + ret.Page); throw new GVLoginException("Didn't get expected redirect"); } } else { Trace.WriteLine("Can't find smsToken in page: " + ret.Page); throw new GVLoginException("Can't find smsToken in page"); } } else { // CheckCookie ... this is a redirect. // TEST: Canadian Proxy: 199.185.95.42 8080 Match m = Regex.Match(ret.Page, "href=\"(.*?)\""); if (m.Success) { // NOTE: this page has URLs that must be HtmlDecoded! OnLoginMessage_Internal("Redirecting..."); ret = http.Get(WebUtil.HtmlDecode(m.Groups[1].Value)); Token_GVX = http.GetCookieString("gvx", "https://www.google.com/voice/m"); if (string.IsNullOrEmpty(Token_GVX)) { throw new GVLoginException("GVX is missing after redirect", ((ret != null) ? ret.Uri : null)); } } else { Trace.WriteLine("Couldn't find continue: " + ret.Page); throw new GVLoginException("Couldn't find continue", ((ret != null) ? ret.Uri : null)); } } } else { Trace.WriteLine("Couldn't find ServiceLogin: "******"ServiceLogin not found", ((ret != null) ? ret.Uri : null)); } try { Match m = Regex.Match(ret.Page, @"appVersion: (.\d*)", RegexOptions.Singleline); if (m.Success) { AppVersion = int.Parse(m.Groups[1].Value); } } catch (FormatException ex) { Trace.WriteLine("Can't get AppVersion: " + ex); } // We're going to fetch the lite mobile page, and pull out an RNR_SE, so we can make calls // the HTML5 calling interface is unsuitable for non-phone devices ret = http.Get("https://www.google.com/voice/m/"); Match rnr = Regex.Match(ret.Page, "name=\"_rnr_se\" value=\"(.*?)\""); if (rnr.Success) { // NOTE: we won't encode here, because Post'ing will encode it. // but this value MUST be encoded before going out! Token_RNR_SE_MustEncode = rnr.Groups[1].Value; } } catch (WebException ex) { Trace.WriteLine("Login Failed: " + ex); // We want to turn HTTP 401 and 403 into a login exception, otherwise the UI will // assume we don't have a network connection. if ((ex.Response as HttpWebResponse).StatusCode == HttpStatusCode.Unauthorized || (ex.Response as HttpWebResponse).StatusCode == HttpStatusCode.Forbidden) { throw new GVLoginException("HTTP 401 or 403 - not authorized"); } else { throw; } } catch (GVLoginException ex) { Trace.WriteLine("Cannot login: "******"RNR_SE: " + Token_RNR_SE_MustEncode); Trace.WriteLine("GVX: " + Token_GVX); Trace.WriteLine("GV App Version: " + AppVersion); new Thread(() => LoadPhones()).Start(); OnLoginMessage_Internal("Loading Contacts..."); AddFeeds(); }