public MainViewModel() { // 조회 내역 처리 Teller.TellerPackageEvent += packages => { Application.Current.Dispatcher.Invoke(new Action(() => { for (var i = packages.Count - 1; i >= 0; i--) // reverse order { var pack = packages[i]; try { Packages.First(new Func <Package, bool>(package => package.Hash == pack.Hash)); } catch (InvalidOperationException) { ProcessPackage(pack); } } Update("Packages"); })); }; Teller.TellerStopEvent += () => { IsFetching = false; server.Update("OFF"); }; // 로그 처리 Teller.TellerLogEvent += log => { Log += $"\n[{DateTime.Now.ToString()}] {log}"; Update("Log"); }; // 서버 시작 server = new StatusServer(); Teller.Log($"http://0.0.0.0:{server.Port} 에서 서비스 상태 웹서버 시작..."); }
protected override List <Packet> FetchPackets(Account account) { var tick = DateTime.Now.Ticks; // open page try { driver = Seeker.Service.GetDriver(); Teller.Log($"[{account.Name}] 페이지를 초기화..."); driver.Manage().Window.Size = new Size(765, 1000); driver.Navigate().GoToUrl("https://obank1.kbstar.com/quics?page=C025255&cc=b028364:b028702");// fill the form Teller.Log($"[{account.Name}] 로딩이 끝나기를 기다림..."); var timeoutSeconds = account.IntervalSeconds; if (timeoutSeconds < 30) { timeoutSeconds = 30; } var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds)); var loading = By.Id("loading"); wait.Until(ExpectedConditions.InvisibilityOfElementLocated(loading)); Teller.Log($"[{account.Name}] 계좌 정보 입력중..."); var account_num = By.Id("account_num"); wait.Until(ExpectedConditions.ElementToBeClickable(account_num)); driver.FindElement(account_num).SendKeys(account.Number.Replace("-", "")); var user_id = By.Id("user_id"); wait.Until(ExpectedConditions.ElementToBeClickable(user_id)); driver.FindElement(user_id).SendKeys(account.UserId); new SelectElement(driver.FindElementById("조회시작년")).SelectByValue(account.From.ToString("yyyy")); new SelectElement(driver.FindElementById("조회시작월")).SelectByValue(account.From.ToString("MM")); new SelectElement(driver.FindElementById("조회시작일")).SelectByValue(account.From.ToString("dd")); new SelectElement(driver.FindElementById("조회끝년")).SelectByValue(account.To.ToString("yyyy")); new SelectElement(driver.FindElementById("조회끝월")).SelectByValue(account.To.ToString("MM")); new SelectElement(driver.FindElementById("조회끝일")).SelectByValue(account.To.ToString("dd")); // open password keypad Teller.Log($"[{account.Name}] 비밀번호 키패드 이미지 추출..."); var password = By.Id("비밀번호"); wait.Until(ExpectedConditions.ElementToBeClickable(password)); driver.FindElement(password).Click(); // save keypad image var keypadImagePath = ContentManager.getPath($@"KB/{tick}.keypad.bmp"); var keypad = By.CssSelector(".keypadWrap img"); wait.Until(ExpectedConditions.ElementToBeClickable(keypad)); driver.GetScreenshot().SaveAsFile(keypadImagePath, ScreenshotImageFormat.Bmp); using (var bitmapKeypad = Bitmap.FromFile(keypadImagePath)) { // analyze keypad image var element = driver.FindElement(keypad); var area = new { left = element.Location.X, center = element.Location.X + element.Size.Width / 2, right = element.Location.X + element.Size.Width, width = element.Size.Width, top = element.Location.Y, height = element.Size.Height, }; var btnLocations = new Dictionary <string, Point>() { { "num1", new Point(area.left + 46, area.top + 70) }, { "num2", new Point(area.center, area.top + 70) }, { "num3", new Point(area.right - 46, area.top + 70) }, { "num4", new Point(area.left + 46, area.top + 130) }, { "xxx1", new Point(area.center, area.top + 130) }, { "num6", new Point(area.right - 46, area.top + 130) }, { "xxx2", new Point(area.left + 46, area.top + 188) }, { "xxx3", new Point(area.center, area.top + 188) }, { "xxx4", new Point(area.right - 46, area.top + 188) }, { "delOne", new Point(area.left + 46, area.top + 246) }, { "xxx5", new Point(area.center, area.top + 246) }, { "delAll", new Point(area.right - 46, area.top + 246) }, { "submit", new Point(area.center, area.top + 300) }, }; // SOLUTION IMAGES' digests var SIGMA = 1; var GAMMA = 1.5; var DEGREE = 180; var solDigests = new Dictionary <string, Digest>() { { "num5", ImagePhash.ComputeDigest(ContentManager.getPath(@"KB/KB_keypad_sol_5.bmp"), SIGMA, GAMMA, DEGREE) }, { "num7", ImagePhash.ComputeDigest(ContentManager.getPath(@"KB/KB_keypad_sol_7.bmp"), SIGMA, GAMMA, DEGREE) }, { "num8", ImagePhash.ComputeDigest(ContentManager.getPath(@"KB/KB_keypad_sol_8.bmp"), SIGMA, GAMMA, DEGREE) }, { "num9", ImagePhash.ComputeDigest(ContentManager.getPath(@"KB/KB_keypad_sol_9.bmp"), SIGMA, GAMMA, DEGREE) }, { "num0", ImagePhash.ComputeDigest(ContentManager.getPath(@"KB/KB_keypad_sol_0.bmp"), SIGMA, GAMMA, DEGREE) }, }; // find each digit of btns by correlation with solution image Teller.Log($"[{account.Name}] 번호별로 이미지 추출, 해싱을 통해 상관관계 분석..."); var solBtnLocations = new Dictionary <string, Point>(); foreach (var btnLocation in btnLocations) { // compare only xxx btns (order shuffled btns 5,7,8,9,0) if (!btnLocation.Key.StartsWith("xxx")) { solBtnLocations.Add(btnLocation.Key, btnLocation.Value); continue; } // crop image var btnImagePath = ContentManager.getPath($@"KB/{tick}.{btnLocation.Key}.bmp"); var rect = new Rectangle(btnLocation.Value.X - 22, btnLocation.Value.Y - 22, 44, 44); using (var bitmap = new Bitmap(rect.Width, rect.Height)) using (var graphic = Graphics.FromImage(bitmap)) { graphic.DrawImage(bitmapKeypad, 0, 0, rect, GraphicsUnit.Pixel); bitmap.Save(btnImagePath); // find each digit for btns var btnDigest = ImagePhash.ComputeDigest(btnImagePath, SIGMA, GAMMA, DEGREE); string answerKey = null; double answerCorr = 0; foreach (var solDigest in solDigests) { var corr = ImagePhash.GetCrossCorrelation(btnDigest, solDigest.Value); if (corr > answerCorr) { answerCorr = corr; answerKey = solDigest.Key; } } // add to solved btn locations try { solBtnLocations.Add(answerKey, btnLocation.Value); Teller.Log($"[{account.Name}] {answerKey} 분석 완료..."); } catch (ArgumentException) { Teller.Log($"[{account.Name}] {btnLocation.Key}를 확정 할 수 없음... 재시작"); throw new NeedToRefetchError(); } } } // now tocuh the keypad as the solution Point locationTo; var zero = driver.FindElement(password); var dx = -area.left + 90; var dy = -area.top + 15; // // helper to adjust dx,dy // driver.ExecuteScript(@" //window.onclick = function(e) { // var d = document.createElement('div'); // d.style.width='2px'; d.style.height='2px'; // d.style.position='absolute'; // d.style.display='block'; // d.style.top=e.clientY+'px'; // d.style.left=e.clientX+'px'; // d.style.background='red'; // d.style.zIndex='10000000'; // document.body.appendChild(d); //}; // "); foreach (var digit in account.Password.ToCharArray()) { locationTo = solBtnLocations["num" + digit]; new Actions(driver).MoveToElement(zero).MoveByOffset(locationTo.X + dx, locationTo.Y + dy).Click().Perform(); Teller.Log($"[{account.Name}] 번호 ({locationTo.X + dx}, {locationTo.Y + dy}) 클릭..."); } locationTo = btnLocations["submit"]; // (373, 554). new Actions(driver).MoveToElement(zero).MoveByOffset(locationTo.X + dx, locationTo.Y + dy).Click().Perform(); Teller.Log($"[{account.Name}] 확인 ({locationTo.X + dx}, {locationTo.Y + dy}) 클릭..."); // submit the form wait.Until(ExpectedConditions.InvisibilityOfElementLocated(keypad)); var submit = By.CssSelector("input[type=submit]"); driver.FindElement(submit).Click(); } // check page loaded var table = By.CssSelector(".tType01"); wait.Until(ExpectedConditions.ElementIsVisible(table)); // reprocessing as Packet def List <Packet> packets = new List <Packet>(); for (var pageNum = 0; true; pageNum++) { Teller.Log($"[{account.Name}] {pageNum + 1} 페이지 파싱 및 분석..."); //driver.GetScreenshot().SaveAsFile(ContentManager.getPath($@"KB/KB_result_{pageNum}.bmp"), ScreenshotImageFormat.Bmp); var trs = driver.FindElementsByCssSelector(".tType01 tbody tr"); if (trs.Count < 2) { Teller.Log($"[{account.Name}] {pageNum + 1} 페이지 거래 내역 없음. 페이지 종료..."); break; // No items } for (var i = 0; i < trs.Count; i++) { var tr = trs[i]; if (i % 2 == 0) { var datetimeTemp = tr.FindElement(By.CssSelector("td:nth-child(1)")).GetAttribute("textContent").Trim(); datetimeTemp = datetimeTemp.Substring(0, 10) + " " + datetimeTemp.Substring(10); var packet = new Packet { Date = Convert.ToDateTime(datetimeTemp), Note = tr.FindElement(By.CssSelector("td:nth-child(2)")).GetAttribute("textContent").Trim(), OutName = tr.FindElement(By.CssSelector("td:nth-child(3)")).GetAttribute("textContent").Trim(), OutAmount = Convert.ToDecimal(tr.FindElement(By.CssSelector("td:nth-child(4)")).GetAttribute("textContent").Trim()), InAmount = Convert.ToDecimal(tr.FindElement(By.CssSelector("td:nth-child(5)")).GetAttribute("textContent").Trim()), Balance = Convert.ToDecimal(tr.FindElement(By.CssSelector("td:nth-child(6)")).GetAttribute("textContent").Trim()), Bank = tr.FindElement(By.CssSelector("td:nth-child(7)")).GetAttribute("textContent").Trim(), Type = tr.FindElement(By.CssSelector("td:nth-child(8)")).GetAttribute("textContent").Trim(), }; packets.Add(packet); } else { packets[(i - 1) / 2].InName = tr.GetAttribute("textContent").Trim(); } } // for pagination try { driver.FindElementByCssSelector(".optionBtnArea .leftArea .next input").Click(); wait.Until(ExpectedConditions.ElementIsVisible(By.Id("loading"))); wait.Until(ExpectedConditions.InvisibilityOfElementLocated(By.Id("loading"))); } catch (NoSuchElementException) { Teller.Log($"[{account.Name}] 페이지 종료..."); break; } } return(packets); } catch (WebDriverException e) { Teller.Log($"[{account.Name}] 가상 브라우저 비정상 종료...\n{e.Message}"); throw new NeedToRefetchError(); } catch (NeedToRefetchError) { throw; } catch (Exception e) { Teller.Log($"[{account.Name}] 처리되지 않은 예외 발생...\n{e.Message}"); throw new NeedToRefetchError(); } finally { // delete temp files DisposeFile($@"KB/{tick}.keypad.bmp"); DisposeFile($@"KB/{tick}.xxx1.bmp"); DisposeFile($@"KB/{tick}.xxx2.bmp"); DisposeFile($@"KB/{tick}.xxx3.bmp"); DisposeFile($@"KB/{tick}.xxx4.bmp"); DisposeFile($@"KB/{tick}.xxx5.bmp"); Dispose(); } }
protected override List <Packet> FetchPackets(Account account) { Teller.Log($"[{account.Name}] 농협은 아직 지원되지 않습니다. (BankSeeker.Lib.Seekers.SeekerNH Class)"); return(null); }