public static void DecrementZoom(RemoteWebDriver aDriver, ViewBounds aViewBounds) { aViewBounds.Zoom = Math.Clamp(aViewBounds.Zoom - 1, 10, 21); aDriver.ExecuteScript($"map.setZoom(arguments[0]);", aViewBounds.Zoom); // Another shitty hack, but we need to wait for the map to zoom out and recache markers, // and this is a lot easier than tryign to hook into the JS event. Util.RandomWait(1500).Wait(); }
private IEnumerable <TargetResponse> QueryTargetList(ViewBounds aViewBounds) { Console.WriteLine("Requesting Address Targets. Request: {0}", aViewBounds.LatLngString); //I think the max limit is 1000 var response = _client.GetAsync($"rest/targets/list?n={aViewBounds.N}&s={aViewBounds.S}&e={aViewBounds.E}&w={aViewBounds.W}&limit=1000").GetAwaiter().GetResult(); var responseString = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); return(JsonConvert.DeserializeObject <IEnumerable <TargetResponse> >(responseString)); }
public static void CenterOnViewBounds(RemoteWebDriver aDriver, ViewBounds aBounds) { if (aBounds != null) { // You can provide your own debug coordinates by // going to the dev console and get values from "viewBounds" object. // Force a recenter and refresh to ensure that our cache is correct before we query for this marker info. // likely unnecessary, but being paranoid here in case a desync happens. aDriver.ExecuteScript($"map.setCenter({aBounds.LatLngString});"); aDriver.ExecuteScript($"refreshMapMarkers();"); } }
public static ViewBounds GetCurrentViewBounds(RemoteWebDriver aDriver) { ViewBounds returnVal = null; // grab the current viewbounds from the user. var objDict = aDriver.ExecuteScript("return viewBounds") as System.Collections.Generic.Dictionary <string, object>; if (objDict != null) { returnVal = new ViewBounds(objDict); } return(returnVal); }
// Gets all available addresses from the current user's view rectangle. public async Task <int> GetTargetAddresses(RemoteWebDriver aDriver, ViewBounds aViewBounds) { var totalResponses = 0; if (aViewBounds != null) { // Ensure that we aren't querying a location that someone else has already sent to. // We also don't support multi-family dwellings yet. (Purely becuase I haven't scripted the UX for it yet) _targetResponses = QueryTargetList(aViewBounds).Where((response) => response.NeedsApplication && response.IsSingleHousehold) .ToList(); double totalTargets = _targetResponses.Count; Console.WriteLine($"Found a total of for {totalTargets} target Ids."); // Using its own variable since the blocking collection // can have elements removed from it from another thread. foreach (var response in _targetResponses) { var addrResponse = await _client.GetAsync($"https://mapthe.vote/rest/addresses/list?targetId={response.Id}"); var responseString = await addrResponse.Content.ReadAsStringAsync(); var addresses = JsonConvert.DeserializeObject <IEnumerable <AddressResponse> >(responseString); // TODO: Support for multi family dwellings. if (addresses.Count() == 1) { var addy = addresses.First(); ParsedAddresses.Add(addy); ++totalResponses; Console.WriteLine($"Queued up submission for Target ID: |{addy.Id}|, {addy.Addr} @ Lat: {addy.Lat}, Lng: {addy.Lng}"); } } } ParsedAddresses.CompleteAdding(); Console.WriteLine($"A total of {totalResponses} marker infos were successfully queried from target IDs."); return(totalResponses); }
static void Main(string[] args) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("MapThe.Vote/Map Address Builder"); Console.WriteLine("By: CJ Stankovich https://github.com/siegeJ"); Console.ForegroundColor = ConsoleColor.White; Util.PreventSleep(); ParseCommandLineArguments(args); SetupDriver(); if (!string.IsNullOrEmpty(JSESSIONID)) { // Can't set a cookie for a domain that we're not yet on. // Go to something that we know will 404 so that we can set cookies // before continuing execution. _driver.Navigate().GoToUrl(@"https://mapthe.vote/404page"); // TODO: Attempt to get cookie from browser. _driver.Manage().Cookies.AddCookie(new OpenQA.Selenium.Cookie("JSESSIONID", JSESSIONID, "mapthe.vote", "/", null)); } // With our JSESSION initialized, we can move onto the actual map. _driver.Navigate().GoToUrl(@"https://mapthe.vote/map"); // Need to manually log in if we don't have a valid cookie. if (string.IsNullOrEmpty(JSESSIONID)) { MapTheVoteScripter.Login(_driver); var jsessionCookie = _driver.Manage().Cookies.GetCookieNamed("JSESSIONID"); if (jsessionCookie != null) { JSESSIONID = jsessionCookie.Value; } } // I hate this, but for some reason waiting for map-msg-button just doesn't work. Util.RandomWait(1000).Wait(); _driver.ClickOnElement("map-msg-button", ElementSearchType.ClassName).Wait(); DateTime startingTime = default; var numFails = 0; var lastNumAddressesParsed = 0; ViewBounds prevBounds = null; var appSubmitter = new ApplicationSubmitter(); while (numFails < 3) { Task <IEnumerable <AddressResponse> > processAppsTask = null; appSubmitter.SubmittedAddresses.Clear(); try { // Wait for user input if we've successfully parsed everything from the // previous run. Otherwise we can use the same bounds again in order to // re-sweep for the markers that we may have missed. // This can happpen var noAddressesParsed = lastNumAddressesParsed == 0; if (noAddressesParsed || (prevBounds == null)) { if (noAddressesParsed && prevBounds != null && (prevBounds.Zoom > ViewBounds.ZOOM_LIMITS.Item1)) { MapTheVoteScripter.DecrementZoom(_driver, prevBounds); // Reset fail count if we're going to try searching around. numFails = 0; } else { // End execution if the user has idled out var success = MapTheVoteScripter.WaitForMarkerSelection(_driver); if (!success) { break; } } prevBounds = MapTheVoteScripter.GetCurrentViewBounds(_driver); } else { Console.WriteLine("Repeating the previous search to find uncached values."); } startingTime = DateTime.Now; var scraper = new AddressScraper(); scraper.Initialize(JSESSIONID); if (prevBounds != null) { MapTheVoteScripter.CenterOnViewBounds(_driver, prevBounds); } var getAddressesTask = scraper.GetTargetAddresses(_driver, prevBounds); processAppsTask = appSubmitter.ProcessApplications(_driver, scraper.ParsedAddresses); Task.WaitAll(getAddressesTask, processAppsTask); lastNumAddressesParsed = getAddressesTask.Result; } catch (Exception e) { Util.LogError(ErrorPhase.Misc, e.ToString()); } // Do this in case the user did something to f**k things up. This way we can still successfully write out the file. // TODO: Thread the filewriting. if (processAppsTask != null && processAppsTask.Status == TaskStatus.Running) { processAppsTask.Wait(); } var lastNumAddressesSubmitted = appSubmitter.SubmittedAddresses.Count; var addressesSubmitted = lastNumAddressesSubmitted != 0; // We wait for 3 consecutive fails before ultimately deciding to call it quits. numFails = addressesSubmitted ? 0 : numFails + 1; if (addressesSubmitted) { var adressesSubmitted = lastNumAddressesSubmitted != 0; Console.WriteLine($"Successfully submitted { lastNumAddressesSubmitted } / { lastNumAddressesParsed } applications."); // Sort our addresses by Zip, City, and then address. appSubmitter.SubmittedAddresses.Sort((lhs, rhs) => { var compareVal = lhs.Zip5.CompareTo(rhs.Zip5); if (compareVal == 0) { compareVal = lhs.City.CompareTo(rhs.City); if (compareVal == 0) { compareVal = lhs.FormattedAddress.CompareTo(rhs.FormattedAddress); } } return(compareVal); }); WriteAddressesFile(AddressesFileName, appSubmitter.SubmittedAddresses); } Console.WriteLine("Completed in {0}", DateTime.Now - startingTime); } CombineAddressesFiles(); Console.WriteLine("Execution complete. Restart the application to send more registration forms."); }