Skip to content

Instantly share code, notes, and snippets.

@jmtsantos
Created April 28, 2017 10:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmtsantos/c9a43a8e134afb40ed297dbd49e92fcb to your computer and use it in GitHub Desktop.
Save jmtsantos/c9a43a8e134afb40ed297dbd49e92fcb to your computer and use it in GitHub Desktop.
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