Created
April 28, 2017 10:07
-
-
Save jmtsantos/c9a43a8e134afb40ed297dbd49e92fcb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cgd | |
import ( | |
"errors" | |
"os" | |
"strconv" | |
"strings" | |
"sync" | |
"time" | |
"better/app/model" | |
log "github.com/Sirupsen/logrus" | |
"github.com/tebeka/selenium" | |
) | |
var ( | |
serverPort = ":4444" | |
serverURL = "http://localhost" + serverPort + "/" | |
// CgdHomeURL first page | |
CgdHomeURL = "https://www.cgd.pt/Pages/default_v2.aspx" | |
// CgdMovementsURL page | |
CgdMovementsURL = "https://caixadirectaonline.cgd.pt/cdo/private/contasaordem/consultaSaldosMovimentos.seam" | |
// ServiceName the id for this service | |
ServiceName = "cgd" | |
) | |
// apenas usado para passar as variaveis todas de login de maneira bonita | |
type accountData struct { | |
username string | |
password string | |
userID int | |
userAccountID int | |
serviceID int | |
currencyID int | |
} | |
func browser() string { | |
browser := os.Getenv("TEST_BROWSER") | |
if len(browser) > 0 { | |
return browser | |
} | |
return "firefox" | |
} | |
func getCaps() selenium.Capabilities { | |
return selenium.Capabilities{ | |
"browserName": browser(), | |
} | |
} | |
func validatePassword(username, password string) error { | |
// validate password | |
if len(password) != 6 { | |
return errors.New("Invalid password lenght") | |
} | |
return nil | |
} | |
func selectAndClick(wd selenium.WebDriver, selector string, element string) (selenium.WebElement, error) { | |
var ( | |
err error | |
textBox selenium.WebElement | |
) | |
// Select account id inputbox | |
time.Sleep(1 * time.Second) | |
if textBox, err = wd.FindElement(selector, element); err != nil { | |
return nil, err | |
} | |
// Click account id inputbox | |
time.Sleep(1 * time.Second) | |
if err = textBox.Click(); err != nil { | |
return nil, err | |
} | |
return textBox, nil | |
} | |
func parseTable(index int, td []selenium.WebElement, data accountData) (model.UserAccountBalance, error) { | |
var ( | |
err error | |
timestamp string | |
txStr1, txStr2 string | |
txFlt float64 | |
funds model.UserAccountBalance | |
txDesc selenium.WebElement | |
) | |
funds.UserID = data.userID | |
funds.UserAccountID = data.userAccountID | |
funds.ServiceID = data.serviceID | |
funds.CurrencyID = data.currencyID | |
// Saca do td o timestamp do extracto | |
if timestamp, err = td[0].Text(); err != nil { | |
log.WithFields(log.Fields{"err": err}).Warn("Error getting values from table, Time") | |
return model.UserAccountBalance{}, err | |
} | |
// Faz o parse do formato da CGD para um objecto time | |
if funds.Time.Time, err = time.Parse("02-01-2006", timestamp); err != nil { | |
log.WithFields(log.Fields{"err": err}).Warn("Error parsing timestamp") | |
return model.UserAccountBalance{}, err | |
} | |
funds.Time.Valid = true | |
// Seleciona o span para depois tirar o atributo title que contem | |
// a descrição da transação | |
if txDesc, err = td[1].FindElement(selenium.ByXPATH, ".//span"); err != nil { | |
log.WithFields(log.Fields{"err": err}).Warn("Error getting values from table, description span") | |
return model.UserAccountBalance{}, err | |
} | |
if funds.Description.String, err = txDesc.GetAttribute("title"); err != nil { | |
log.WithFields(log.Fields{"err": err}).Warn("Error getting values from table, description text") | |
return model.UserAccountBalance{}, err | |
} | |
funds.Description.Valid = true | |
// Extrair o débito | |
if txStr1, err = td[2].Text(); err != nil { | |
log.WithFields(log.Fields{"err": err}).Warn("Error getting values from table, Transaction") | |
return model.UserAccountBalance{}, err | |
} | |
if strings.TrimSpace(txStr1) != "" { | |
txStr2 = strings.Replace(txStr1, ".", "", 1) | |
if txFlt, err = strconv.ParseFloat(strings.Replace(txStr2, ",", ".", 1), 32); err != nil { | |
log.WithFields(log.Fields{"err": err}).Warn("Error parsing Exposure") | |
return model.UserAccountBalance{}, err | |
} | |
funds.TransactionValue = int(txFlt * 100) | |
} | |
// Extrarir o crédito | |
if txStr1, err = td[3].Text(); err != nil { | |
log.WithFields(log.Fields{"err": err}).Warn("Error getting values from table, Transaction Debit") | |
return model.UserAccountBalance{}, err | |
} | |
if strings.TrimSpace(txStr1) != "" { | |
txStr2 = strings.Replace(txStr1, ".", "", 1) | |
if txFlt, err = strconv.ParseFloat(strings.Replace(txStr2, ",", ".", 1), 32); err != nil { | |
log.WithFields(log.Fields{"err": err}).Warn("Error parsing Exposure") | |
return model.UserAccountBalance{}, err | |
} | |
funds.TransactionValue = int(txFlt * 100) | |
} | |
// Saldo apos movimento | |
if txStr1, err = td[6].Text(); err != nil { | |
log.WithFields(log.Fields{"err": err}).Warn("Error getting values from table, Balance") | |
return model.UserAccountBalance{}, err | |
} | |
txStr2 = strings.Replace(txStr1, ".", "", 1) | |
if txFlt, err = strconv.ParseFloat(strings.Replace(txStr2, ",", ".", 1), 32); err != nil { | |
log.WithFields(log.Fields{"err": err}).Warn("Error parsing Balance") | |
return model.UserAccountBalance{}, err | |
} | |
funds.Balance = int(txFlt * 100) | |
// Saldo contabilistico após movimento | |
// temp, err = td[5].Text() mesma coisa mas para o elem 5 | |
return funds, nil | |
} | |
// Simula navegação até à consulta de saldo | |
func checkBalance(data accountData) { | |
var ( | |
err error | |
wd selenium.WebDriver | |
elems []selenium.WebElement | |
element selenium.WebElement | |
) | |
// Setup selenium | |
if wd, err = selenium.NewRemote(getCaps(), ""); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("Cant start session") | |
return | |
} | |
defer wd.Quit() | |
// Open first url | |
if err = wd.Get(CgdHomeURL); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("Error opening url") | |
return | |
} | |
// Silly bypass | |
script := `document.getElementsByClassName("direct-wrapper")[0].className += "open"` | |
if _, err = wd.ExecuteScript(script, nil); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("Script exec") | |
return | |
} | |
// Select and click input_cx1 | |
if element, err = selectAndClick(wd, selenium.ByID, "input_cx1"); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("selectAndClick input_cx1") | |
return | |
} | |
// Send username | |
time.Sleep(1 * time.Second) | |
if err = element.SendKeys(data.username); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("SendKeys") | |
return | |
} | |
// Select and click login_btn_1 | |
if _, err = selectAndClick(wd, selenium.ByID, "login_btn_1"); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("selectAndClick login_btn_1") | |
return | |
} | |
// Se ocorrerem muitos erros nesta fase, fazer um click | |
// neste elemento direct-link desktop open | |
// TODO este elemento muda muitas vezes o ID fazer um loop para testar entre j_id37 ao j_id47 | |
if _, err = selectAndClick(wd, selenium.ByID, "j_id39"); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("selectAndClick j_id39") | |
return | |
} | |
// Safe login bypass | |
time.Sleep(2 * time.Second) | |
script = `btClick(this, arguments[0].charAt(0)); btClick(this, arguments[0].charAt(1)); | |
btClick(this, arguments[0].charAt(2)); btClick(this, arguments[0].charAt(3)); | |
btClick(this, arguments[0].charAt(4)); btClick(this, arguments[0].charAt(5));` | |
args := []interface{}{data.password} | |
if _, err = wd.ExecuteScript(script, args); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("Script exec") | |
return | |
} | |
// Select and click loginForm:submit | |
if _, err = selectAndClick(wd, selenium.ByID, "loginForm:submit"); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("selectAndClick loginForm:submit") | |
return | |
} | |
// Select and click botao_ok | |
if _, err = selectAndClick(wd, selenium.ByClassName, "botao_ok"); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("selectAndClick botao_ok") | |
return | |
} | |
// Levantamento de movimentações de conta | |
if err = wd.Get(CgdMovementsURL); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("Erro opening url") | |
return | |
} | |
// Get all table elements | |
if elems, err = wd.FindElements(selenium.ByXPATH, "//table[@class='mgtop bordertable fltl clear']/tbody/tr"); err != nil { | |
log.WithFields(log.Fields{"err": err, "username": data.username}).Warn("Error getting values from table") | |
return | |
} | |
// Parse data | |
var tdElems []selenium.WebElement | |
for i, e := range elems { | |
if i%2 != 0 { | |
if tdElems, err = e.FindElements(selenium.ByXPATH, ".//td"); err != nil { | |
log.WithFields(log.Fields{"err": err}).Warn("Error getting values from table, table column") | |
return | |
} | |
if len(tdElems) > 6 { | |
var funds model.UserAccountBalance | |
if funds, err = parseTable(i, tdElems, data); err != nil { | |
continue | |
} else { | |
funds.Save() | |
} | |
} | |
} | |
} | |
} | |
func workerRoutine(userAcc model.UserAccount) { | |
var ( | |
cgdConfig model.CgdConfig | |
data accountData | |
) | |
cgdConfig.UserAccountID = userAcc.ID | |
// Fill in all the data | |
data.username = userAcc.Username | |
data.password = userAcc.Password | |
data.userID = userAcc.UserID | |
data.userAccountID = userAcc.ID | |
data.serviceID = userAcc.ServiceID | |
data.currencyID = 1 | |
for { | |
// Sleep first, dev friendly | |
cgdConfig.GetByUserAccountID() | |
time.Sleep(time.Minute * time.Duration(cgdConfig.RefreshTime)) | |
checkBalance(data) | |
time.Sleep(time.Second * 30) | |
} | |
} | |
// Run start the cgd observer service | |
func Run() { | |
var ( | |
service model.Service | |
userAccs model.UserAccount | |
wg sync.WaitGroup | |
) | |
log.Println("[observer][cgd] starting") | |
// Get the service id | |
service.Name = ServiceName | |
service.GetByName() | |
// Get all cgd accounts | |
userAccs.ServiceID = service.ID | |
userAccs.GetByServiceID() | |
wg.Add(1) // TODO runs forever | |
for _, ac := range userAccs.Set { | |
go workerRoutine(ac) | |
} | |
wg.Wait() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment