Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc0b0a64e2 | ||
|
|
1e01058c7c | ||
|
|
5c557ea713 | ||
|
|
8b226294a2 | ||
|
|
f6fc366dd4 | ||
|
|
1139fef0ab |
3
bank.go
3
bank.go
@@ -7,10 +7,13 @@ const (
|
|||||||
Citi Bank = iota + 1
|
Citi Bank = iota + 1
|
||||||
UCCU Bank = iota + 1
|
UCCU Bank = iota + 1
|
||||||
BankOfAmerica Bank = iota + 1
|
BankOfAmerica Bank = iota + 1
|
||||||
|
Fidelity Bank = iota + 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b Bank) String() string {
|
func (b Bank) String() string {
|
||||||
switch b {
|
switch b {
|
||||||
|
case Fidelity:
|
||||||
|
return "Fidelity"
|
||||||
case BankOfAmerica:
|
case BankOfAmerica:
|
||||||
return "BankOfAmerica"
|
return "BankOfAmerica"
|
||||||
case Chase:
|
case Chase:
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ type Config struct {
|
|||||||
EmailUser string
|
EmailUser string
|
||||||
EmailPass string
|
EmailPass string
|
||||||
EmailIMAP string
|
EmailIMAP string
|
||||||
|
EmailLimit int
|
||||||
TodoAddr string
|
TodoAddr string
|
||||||
TodoToken string
|
TodoToken string
|
||||||
TodoList string
|
TodoList string
|
||||||
@@ -49,6 +50,7 @@ func NewConfig() Config {
|
|||||||
as.Append(args.STRING, "emailuser", "email username", "breellocaldev@gmail.com")
|
as.Append(args.STRING, "emailuser", "email username", "breellocaldev@gmail.com")
|
||||||
as.Append(args.STRING, "emailpass", "email password", "diblloewfncwssof")
|
as.Append(args.STRING, "emailpass", "email password", "diblloewfncwssof")
|
||||||
as.Append(args.STRING, "emailimap", "email imap", "imap.gmail.com:993")
|
as.Append(args.STRING, "emailimap", "email imap", "imap.gmail.com:993")
|
||||||
|
as.Append(args.INT, "emaillimit", "email limit", 0)
|
||||||
|
|
||||||
as.Append(args.STRING, "uploader", "todo, ledger, pttodo", "todo")
|
as.Append(args.STRING, "uploader", "todo, ledger, pttodo", "todo")
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ func NewConfig() Config {
|
|||||||
as.Append(args.STRING, "todolist", "todo list", "")
|
as.Append(args.STRING, "todolist", "todo list", "")
|
||||||
as.Append(args.STRING, "todotag", "todo tag", "expense")
|
as.Append(args.STRING, "todotag", "todo tag", "expense")
|
||||||
|
|
||||||
as.Append(args.STRING, "banks", "uccu,citi,chase,bankofamerica", "uccu,citi,chase,bankofamerica")
|
as.Append(args.STRING, "banks", "uccu,citi,chase,bankofamerica,fidelity", "uccu,citi,chase,bankofamerica,fidelity")
|
||||||
as.Append(args.STRING, "accounts", "regex to include filter accounts", ".*")
|
as.Append(args.STRING, "accounts", "regex to include filter accounts", ".*")
|
||||||
as.Append(args.STRING, "not-accounts", "regex to exclude filter accounts", "zzzzzz")
|
as.Append(args.STRING, "not-accounts", "regex to exclude filter accounts", "zzzzzz")
|
||||||
|
|
||||||
@@ -87,6 +89,7 @@ func NewConfig() Config {
|
|||||||
EmailUser: as.GetString("emailuser"),
|
EmailUser: as.GetString("emailuser"),
|
||||||
EmailPass: as.GetString("emailpass"),
|
EmailPass: as.GetString("emailpass"),
|
||||||
EmailIMAP: as.GetString("emailimap"),
|
EmailIMAP: as.GetString("emailimap"),
|
||||||
|
EmailLimit: as.GetInt("emaillimit"),
|
||||||
TodoAddr: as.GetString("todoaddr"),
|
TodoAddr: as.GetString("todoaddr"),
|
||||||
TodoTag: as.GetString("todotag"),
|
TodoTag: as.GetString("todotag"),
|
||||||
AccountsPattern: as.GetString("accounts"),
|
AccountsPattern: as.GetString("accounts"),
|
||||||
@@ -98,6 +101,7 @@ func NewConfig() Config {
|
|||||||
Chase: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(Chase.String())),
|
Chase: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(Chase.String())),
|
||||||
Citi: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(Citi.String())),
|
Citi: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(Citi.String())),
|
||||||
UCCU: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(UCCU.String())),
|
UCCU: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(UCCU.String())),
|
||||||
|
Fidelity: strings.Contains(strings.ToLower(as.GetString("banks")), strings.ToLower(Fidelity.String())),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
log.Printf("config: %+v", config)
|
log.Printf("config: %+v", config)
|
||||||
|
|||||||
7
go.mod
7
go.mod
@@ -4,6 +4,7 @@ go 1.17
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
local/args v0.0.0-00010101000000-000000000000
|
local/args v0.0.0-00010101000000-000000000000
|
||||||
local/oauth2 v0.0.0-00010101000000-000000000000
|
local/oauth2 v0.0.0-00010101000000-000000000000
|
||||||
local/sandbox/contact/contact v0.0.0-00010101000000-000000000000
|
local/sandbox/contact/contact v0.0.0-00010101000000-000000000000
|
||||||
@@ -16,10 +17,7 @@ require (
|
|||||||
github.com/abbot/go-http-auth v0.4.0 // indirect
|
github.com/abbot/go-http-auth v0.4.0 // indirect
|
||||||
github.com/aws/aws-sdk-go v1.15.81 // indirect
|
github.com/aws/aws-sdk-go v1.15.81 // indirect
|
||||||
github.com/boltdb/bolt v1.3.1 // indirect
|
github.com/boltdb/bolt v1.3.1 // indirect
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
|
|
||||||
github.com/buraksezer/consistent v0.9.0 // indirect
|
|
||||||
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e // indirect
|
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e // indirect
|
||||||
github.com/cespare/xxhash v1.1.0 // indirect
|
|
||||||
github.com/emersion/go-imap v1.2.0 // indirect
|
github.com/emersion/go-imap v1.2.0 // indirect
|
||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||||
github.com/go-stack/stack v1.8.0 // indirect
|
github.com/go-stack/stack v1.8.0 // indirect
|
||||||
@@ -52,13 +50,12 @@ require (
|
|||||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect
|
golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba // indirect
|
golang.org/x/oauth2 v0.0.0-20181120190819-8f65e3013eba // indirect
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||||
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2 // indirect
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||||
google.golang.org/api v0.0.0-20181120235003-faade3cbb06a // indirect
|
google.golang.org/api v0.0.0-20181120235003-faade3cbb06a // indirect
|
||||||
google.golang.org/appengine v1.3.0 // indirect
|
google.golang.org/appengine v1.3.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.42.0 // indirect
|
gopkg.in/ini.v1 v1.42.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
|
||||||
local/logb v0.0.0-00010101000000-000000000000 // indirect
|
local/logb v0.0.0-00010101000000-000000000000 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
13
go.sum
13
go.sum
@@ -5,8 +5,6 @@ github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9a
|
|||||||
github.com/Azure/azure-storage-blob-go v0.0.0-20181023070848-cf01652132cc/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y=
|
github.com/Azure/azure-storage-blob-go v0.0.0-20181023070848-cf01652132cc/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
|
||||||
github.com/Unknwon/goconfig v0.0.0-20181105214110-56bd8ab18619 h1:6X8iB881g299aNEv6KXrcjL31iLOH7yA6NXoQX+MbDg=
|
github.com/Unknwon/goconfig v0.0.0-20181105214110-56bd8ab18619 h1:6X8iB881g299aNEv6KXrcjL31iLOH7yA6NXoQX+MbDg=
|
||||||
github.com/Unknwon/goconfig v0.0.0-20181105214110-56bd8ab18619/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw=
|
github.com/Unknwon/goconfig v0.0.0-20181105214110-56bd8ab18619/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw=
|
||||||
github.com/a8m/tree v0.0.0-20180321023834-3cf936ce15d6/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg=
|
github.com/a8m/tree v0.0.0-20180321023834-3cf936ce15d6/go.mod h1:FSdwKX97koS5efgm8WevNf7XS3PqtyFkKDDXrz778cg=
|
||||||
@@ -18,14 +16,8 @@ github.com/aws/aws-sdk-go v1.15.81/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3A
|
|||||||
github.com/billziss-gh/cgofuse v1.1.0/go.mod h1:LJjoaUojlVjgo5GQoEJTcJNqZJeRU0nCR84CyxKt2YM=
|
github.com/billziss-gh/cgofuse v1.1.0/go.mod h1:LJjoaUojlVjgo5GQoEJTcJNqZJeRU0nCR84CyxKt2YM=
|
||||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
|
||||||
github.com/buraksezer/consistent v0.9.0 h1:Zfs6bX62wbP3QlbPGKUhqDw7SmNkOzY5bHZIYXYpR5g=
|
|
||||||
github.com/buraksezer/consistent v0.9.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw=
|
|
||||||
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e h1:mQTN05gz0rDZSABqKMzAPMb5ATWcvvdMljRzEh0LjBo=
|
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e h1:mQTN05gz0rDZSABqKMzAPMb5ATWcvvdMljRzEh0LjBo=
|
||||||
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e/go.mod h1:alXX+s7a4cKaIprgjeEboqi4Tm7XR/HXEwUTxUV/ywU=
|
github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e/go.mod h1:alXX+s7a4cKaIprgjeEboqi4Tm7XR/HXEwUTxUV/ywU=
|
||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
|
||||||
github.com/coreos/bbolt v0.0.0-20180318001526-af9db2027c98/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v0.0.0-20180318001526-af9db2027c98/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
|
github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@@ -172,8 +164,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
|
|||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
|
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
@@ -232,8 +222,9 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2 h1:T5DasATyLQfmbTpfEXx/IOL9vfjzW6up+ZDkmHvIf2s=
|
|
||||||
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
|
||||||
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
|||||||
3
main.go
3
main.go
@@ -12,6 +12,7 @@ func main() {
|
|||||||
IMAP: config.EmailIMAP,
|
IMAP: config.EmailIMAP,
|
||||||
From: config.EmailUser,
|
From: config.EmailUser,
|
||||||
Password: config.EmailPass,
|
Password: config.EmailPass,
|
||||||
|
Limit: config.EmailLimit,
|
||||||
}
|
}
|
||||||
emails, err := emailer.Read()
|
emails, err := emailer.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -33,7 +34,7 @@ func main() {
|
|||||||
log.Printf("skipping match account antipattern %q vs %q", config.AccountsAntiPattern, transaction.Account)
|
log.Printf("skipping match account antipattern %q vs %q", config.AccountsAntiPattern, transaction.Account)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if _, err := config.Storage.Get(transaction.ID); err == nil {
|
if v, err := config.Storage.Get(transaction.ID); err == nil || string(v) == transaction.String() {
|
||||||
log.Println("skipping duplicate transaction:", transaction)
|
log.Println("skipping duplicate transaction:", transaction)
|
||||||
} else {
|
} else {
|
||||||
if err := Upload(config, transaction); err != nil {
|
if err := Upload(config, transaction); err != nil {
|
||||||
|
|||||||
80
scrape.go
80
scrape.go
@@ -15,6 +15,7 @@ type scraper interface {
|
|||||||
scrape(*mail.Message) ([]*Transaction, error)
|
scrape(*mail.Message) ([]*Transaction, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fidelityScraper struct{}
|
||||||
type bankOfAmericaScraper struct{}
|
type bankOfAmericaScraper struct{}
|
||||||
type chaseScraper struct{}
|
type chaseScraper struct{}
|
||||||
type citiScraper struct{}
|
type citiScraper struct{}
|
||||||
@@ -30,13 +31,16 @@ func Scrape(m *mail.Message, banks map[Bank]bool) ([]*Transaction, error) {
|
|||||||
|
|
||||||
func buildScraper(m *mail.Message, banks map[Bank]bool) (scraper, error) {
|
func buildScraper(m *mail.Message, banks map[Bank]bool) (scraper, error) {
|
||||||
subject := fmt.Sprint(m.Header["Subject"])
|
subject := fmt.Sprint(m.Header["Subject"])
|
||||||
if !containsAny(subject, "transaction", "report", "Transaction", "payment", "Payment") {
|
if !containsAny(subject, "transaction", "report", "Transaction", "payment", "Payment", "Deposit", "Withdrawal") {
|
||||||
return nil, errors.New("cannot build scraper for subject " + subject)
|
return nil, errors.New("cannot build scraper for subject " + subject)
|
||||||
}
|
}
|
||||||
from := fmt.Sprint(m.Header["From"])
|
from := fmt.Sprint(m.Header["From"])
|
||||||
if strings.Contains(from, "Chase") && banks[Chase] {
|
if strings.Contains(from, "Chase") && banks[Chase] {
|
||||||
return newChaseScraper(), nil
|
return newChaseScraper(), nil
|
||||||
}
|
}
|
||||||
|
if strings.Contains(from, "Fidelity") && banks[Fidelity] {
|
||||||
|
return newFidelityScraper(), nil
|
||||||
|
}
|
||||||
if strings.Contains(from, "Bank of America") && banks[BankOfAmerica] {
|
if strings.Contains(from, "Bank of America") && banks[BankOfAmerica] {
|
||||||
return newBankOfAmericaScraper(), nil
|
return newBankOfAmericaScraper(), nil
|
||||||
}
|
}
|
||||||
@@ -49,6 +53,10 @@ func buildScraper(m *mail.Message, banks map[Bank]bool) (scraper, error) {
|
|||||||
return nil, errors.New("unknown sender: " + from)
|
return nil, errors.New("unknown sender: " + from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newFidelityScraper() scraper {
|
||||||
|
return &fidelityScraper{}
|
||||||
|
}
|
||||||
|
|
||||||
func newBankOfAmericaScraper() scraper {
|
func newBankOfAmericaScraper() scraper {
|
||||||
return &bankOfAmericaScraper{}
|
return &bankOfAmericaScraper{}
|
||||||
}
|
}
|
||||||
@@ -210,6 +218,52 @@ func (c *uccuScraper) scrape(m *mail.Message) ([]*Transaction, error) {
|
|||||||
return []*Transaction{transaction}, nil
|
return []*Transaction{transaction}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *fidelityScraper) scrape(m *mail.Message) ([]*Transaction, error) {
|
||||||
|
subject := fmt.Sprint(m.Header["Subject"])
|
||||||
|
if strings.Contains(subject, "Debit Withdrawal") {
|
||||||
|
return c.scrapeWithdrawal(m)
|
||||||
|
}
|
||||||
|
if strings.Contains(subject, "Deposit Received") {
|
||||||
|
return c.scrapeDeposit(m)
|
||||||
|
}
|
||||||
|
panic(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fidelityScraper) scrapeDeposit(m *mail.Message) ([]*Transaction, error) {
|
||||||
|
b, err := ioutil.ReadAll(m.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fidelAcc, _ := findSubstringBetween(b, "Account: XXXXX", "\n")
|
||||||
|
|
||||||
|
transaction := NewTransaction(
|
||||||
|
fmt.Sprintf("%s-%s", Fidelity, fidelAcc),
|
||||||
|
"?.??",
|
||||||
|
"misc",
|
||||||
|
fmt.Sprint(m.Header["Date"]),
|
||||||
|
Fidelity,
|
||||||
|
)
|
||||||
|
return []*Transaction{transaction}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fidelityScraper) scrapeWithdrawal(m *mail.Message) ([]*Transaction, error) {
|
||||||
|
b, err := ioutil.ReadAll(m.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
amount, amountOk := findSubstringBetween(b, "in the amount of $", " ")
|
||||||
|
fidelAcc, fidelAccOk := findSubstringBetween(b, "For account ending in ", ":")
|
||||||
|
acc, accOk := findSubstringBetween(b, "in the amount of $"+amount+" by ", ".")
|
||||||
|
|
||||||
|
if amount == "" || acc == "" {
|
||||||
|
return nil, fmt.Errorf("no amount/account found: fidelAcc=%v,fidelAccOk=%v, acc=%v,accOk=%v, amount=%v,amountOk=%v", fidelAcc, fidelAccOk, acc, accOk, amount, amountOk)
|
||||||
|
}
|
||||||
|
transaction := NewTransaction(fmt.Sprintf("%s-%s", Fidelity, fidelAcc), amount, acc, fmt.Sprint(m.Header["Date"]), Fidelity)
|
||||||
|
return []*Transaction{transaction}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *bankOfAmericaScraper) scrape(m *mail.Message) ([]*Transaction, error) {
|
func (c *bankOfAmericaScraper) scrape(m *mail.Message) ([]*Transaction, error) {
|
||||||
subject := fmt.Sprint(m.Header["Subject"])
|
subject := fmt.Sprint(m.Header["Subject"])
|
||||||
if strings.Contains(subject, "Credit card transaction") {
|
if strings.Contains(subject, "Credit card transaction") {
|
||||||
@@ -227,8 +281,8 @@ func (c *bankOfAmericaScraper) scrapeCharge(m *mail.Message) ([]*Transaction, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
amount := c.findFloatAfter(b, "Amount: $")
|
amount := findFloatAfter(b, "Amount: $")
|
||||||
acc := string(c.findLineAfter(b, "Where: "))
|
acc := string(findLineAfter(b, "Where: "))
|
||||||
|
|
||||||
if amount == "" || acc == "" {
|
if amount == "" || acc == "" {
|
||||||
return nil, errors.New("no amount/account found")
|
return nil, errors.New("no amount/account found")
|
||||||
@@ -242,7 +296,7 @@ func (c *bankOfAmericaScraper) scrapePayment(m *mail.Message) ([]*Transaction, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
amount := "-" + c.findFloatAfter(b, "Payment: $")
|
amount := "-" + findFloatAfter(b, "Payment: $")
|
||||||
acc := "Payment"
|
acc := "Payment"
|
||||||
if amount == "" || acc == "" {
|
if amount == "" || acc == "" {
|
||||||
return nil, errors.New("no amount/account found")
|
return nil, errors.New("no amount/account found")
|
||||||
@@ -251,8 +305,20 @@ func (c *bankOfAmericaScraper) scrapePayment(m *mail.Message) ([]*Transaction, e
|
|||||||
return []*Transaction{transaction}, nil
|
return []*Transaction{transaction}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *bankOfAmericaScraper) findFloatAfter(b []byte, prefix string) string {
|
func findSubstringBetween(b []byte, prefix, suffix string) (string, bool) {
|
||||||
amount := string(c.findLineAfter(b, prefix))
|
byPre := bytes.Split(b, []byte(prefix))
|
||||||
|
if len(byPre) < 2 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
bySuff := bytes.Split(byPre[1], []byte(suffix))
|
||||||
|
if len(bySuff) < 2 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return string(bySuff[0]), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func findFloatAfter(b []byte, prefix string) string {
|
||||||
|
amount := string(findLineAfter(b, prefix))
|
||||||
words := strings.Split(amount, " ")
|
words := strings.Split(amount, " ")
|
||||||
lastword := words[len(words)-1]
|
lastword := words[len(words)-1]
|
||||||
escapedfloat := strings.TrimPrefix(lastword, "$")
|
escapedfloat := strings.TrimPrefix(lastword, "$")
|
||||||
@@ -261,7 +327,7 @@ func (c *bankOfAmericaScraper) findFloatAfter(b []byte, prefix string) string {
|
|||||||
return amount
|
return amount
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *bankOfAmericaScraper) findLineAfter(b []byte, prefix string) []byte {
|
func findLineAfter(b []byte, prefix string) []byte {
|
||||||
for _, line := range bytes.Split(b, []byte("\n")) {
|
for _, line := range bytes.Split(b, []byte("\n")) {
|
||||||
if bytes.HasPrefix(line, []byte(prefix)) {
|
if bytes.HasPrefix(line, []byte(prefix)) {
|
||||||
return bytes.TrimSpace(bytes.TrimPrefix(line, []byte(prefix)))
|
return bytes.TrimSpace(bytes.TrimPrefix(line, []byte(prefix)))
|
||||||
|
|||||||
@@ -179,6 +179,76 @@ func TestScrapeBofAPayment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestScrapeFidelityDeposit(t *testing.T) {
|
||||||
|
b, err := ioutil.ReadFile("./testdata/fidelity.deposit.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
message := &mail.Message{
|
||||||
|
Header: map[string][]string{
|
||||||
|
"Subject": []string{"Fidelity Alerts: Deposit Received"},
|
||||||
|
},
|
||||||
|
Body: bytes.NewReader(b),
|
||||||
|
}
|
||||||
|
fidelity := &fidelityScraper{}
|
||||||
|
|
||||||
|
gots, err := fidelity.scrape(message)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(gots) != 1 {
|
||||||
|
t.Fatal(len(gots))
|
||||||
|
}
|
||||||
|
got := gots[0]
|
||||||
|
want := Transaction{
|
||||||
|
ID: got.ID,
|
||||||
|
Bank: Fidelity,
|
||||||
|
Amount: "?.??",
|
||||||
|
Vendor: "misc",
|
||||||
|
Date: "[]",
|
||||||
|
Account: Fidelity.String() + "-5576",
|
||||||
|
}
|
||||||
|
if *got != want {
|
||||||
|
t.Fatalf("want:\n\t%+v, got\n\t%+v", want, *got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScrapeFidelityWithdrawal(t *testing.T) {
|
||||||
|
b, err := ioutil.ReadFile("./testdata/fidelity.withdrawal.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
message := &mail.Message{
|
||||||
|
Header: map[string][]string{
|
||||||
|
"Subject": []string{"Fidelity Alerts - Direct Debit Withdrawal"},
|
||||||
|
},
|
||||||
|
Body: bytes.NewReader(b),
|
||||||
|
}
|
||||||
|
fidelity := &fidelityScraper{}
|
||||||
|
|
||||||
|
gots, err := fidelity.scrape(message)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(gots) != 1 {
|
||||||
|
t.Fatal(len(gots))
|
||||||
|
}
|
||||||
|
got := gots[0]
|
||||||
|
want := Transaction{
|
||||||
|
ID: got.ID,
|
||||||
|
Bank: Fidelity,
|
||||||
|
Amount: "1.00",
|
||||||
|
Vendor: "CHASE CREDIT CRD",
|
||||||
|
Date: "[]",
|
||||||
|
Account: Fidelity.String() + "-5576",
|
||||||
|
}
|
||||||
|
if *got != want {
|
||||||
|
t.Fatalf("want:\n\t%+v, got\n\t%+v", want, *got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestScrapeBofACharge(t *testing.T) {
|
func TestScrapeBofACharge(t *testing.T) {
|
||||||
b, err := ioutil.ReadFile("./testdata/bofa.charge.txt")
|
b, err := ioutil.ReadFile("./testdata/bofa.charge.txt")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
29
testdata/fidelity.deposit.txt
vendored
Normal file
29
testdata/fidelity.deposit.txt
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
Account: XXXXX5576
|
||||||
|
|
||||||
|
A deposit to your account was received on 12/08/2022.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Important information:
|
||||||
|
Fidelity automatically emails certain alerts to customers who have provided
|
||||||
|
an email address. You can see the terms which govern these alerts at
|
||||||
|
https://www.fidelity.com/customer-service/alerts-agreement. If you would
|
||||||
|
prefer to not receive these alerts, please change your preferences at
|
||||||
|
https://scs.fidelity.com/customeronly/alerts.shtml.
|
||||||
|
|
||||||
|
Review Fidelity's Terms of Use for Third Party Content and Research at
|
||||||
|
https://www.fidelity.com/terms-of-use#Third.
|
||||||
|
|
||||||
|
If your email has changed, please update your email address
|
||||||
|
at https://alertable.fidelity.com/ftgw/alerts/GetUserDeliveryDevices to continue to
|
||||||
|
receive your alerts.
|
||||||
|
|
||||||
|
Fidelity Brokerage Services LLC, Member NYSE, SIPC
|
||||||
|
|
||||||
|
EMAIL REF# 537048
|
||||||
|
|
||||||
|
Copyright 2022 FMR LLC
|
||||||
|
All rights reserved. Important Legal Information
|
||||||
|
at http://www.fidelity.com/terms-of-use.
|
||||||
|
|
||||||
49
testdata/fidelity.withdrawal.txt
vendored
Normal file
49
testdata/fidelity.withdrawal.txt
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
|
||||||
|
For account ending in 5576:
|
||||||
|
Money was withdrawn from your account through a direct debit in the amount of $1.00 by CHASE CREDIT CRD.
|
||||||
|
|
||||||
|
If you authorized this transaction, no action is needed.
|
||||||
|
If you did not authorize this transaction, please contact us immediately at 800-343-3548.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Important information:
|
||||||
|
Fidelity automatically emails certain alerts to customers who have provided
|
||||||
|
an email address. You can see the terms which govern these alerts at
|
||||||
|
https://alertable.fidelity.com/alerts/help/agreement.html. If you would
|
||||||
|
prefer to not receive these alerts, please change your preferences at
|
||||||
|
https://scs.fidelity.com/customeronly/alerts.shtml.
|
||||||
|
This new alerts service will not affect delivery of paper communications
|
||||||
|
you are scheduled to receive.
|
||||||
|
|
||||||
|
To stop receipt of alerts, modify your preferences on the Existing Alerts page
|
||||||
|
at https://scs.fidelity.com/customeronly/fens.shtml, or temporarily
|
||||||
|
stop/restart alerts at
|
||||||
|
https://scs.fidelity.com/customeronly/fens_alertstatus.shtml.
|
||||||
|
|
||||||
|
Fidelity offers access to a broader range of third-party research at
|
||||||
|
http://personal.fidelity.com/research/stocks/content/stocksindex.shtml.
|
||||||
|
Fidelity is not recommending or endorsing any third-party research by making it
|
||||||
|
available to its customers or by notifying customers of its availability.
|
||||||
|
|
||||||
|
Review Fidelity's Terms of Use for Third Party Content and Research at
|
||||||
|
http://activequote.fidelity.com/rtrnews/terms.html.
|
||||||
|
|
||||||
|
If your email has changed, please update your email address
|
||||||
|
at https://scs.fidelity.com/customeronly/fens_profile.shtml to continue to
|
||||||
|
receive your alerts.
|
||||||
|
|
||||||
|
Read Fidelity's Commitment to Privacy
|
||||||
|
at http://personal.fidelity.com/global/search/content/privacy.html.tvsr.
|
||||||
|
|
||||||
|
Visit Fidelity's Home Page
|
||||||
|
http://www.fidelity.com/
|
||||||
|
|
||||||
|
Fidelity Brokerage Services LLC, Member NYSE, SIPC, 900 Salem Street, Smithfield, RI 02917
|
||||||
|
|
||||||
|
EMAIL REF# 537048
|
||||||
|
|
||||||
|
Copyright 2022 FMR LLC
|
||||||
|
All rights reserved. Important Legal Information
|
||||||
|
at http://personal.fidelity.com/misc/legal/legal.html.tvsr.
|
||||||
|
|
||||||
Reference in New Issue
Block a user