mirror of
https://github.com/aclindsa/ofxgo.git
synced 2024-12-25 22:43:21 -05:00
Add command-line client
This commit is contained in:
parent
6491311dbc
commit
8d8ee0016d
82
cmd/ofx/bankdownload.go
Normal file
82
cmd/ofx/bankdownload.go
Normal file
@ -0,0 +1,82 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/aclindsa/ofxgo"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var downloadCommand = Command{
|
||||
Name: "download-bank",
|
||||
Description: "Download a bank account statement to a file",
|
||||
Flags: flag.NewFlagSet("download-bank", flag.ExitOnError),
|
||||
CheckFlags: downloadCheckFlags,
|
||||
Do: download,
|
||||
}
|
||||
|
||||
var filename, bankId, acctId, acctType string
|
||||
|
||||
func init() {
|
||||
defineServerFlags(downloadCommand.Flags)
|
||||
downloadCommand.Flags.StringVar(&filename, "filename", "./download.ofx", "The file to save to")
|
||||
downloadCommand.Flags.StringVar(&bankId, "bankid", "", "BankId (from `get-accounts` subcommand)")
|
||||
downloadCommand.Flags.StringVar(&acctId, "acctid", "", "AcctId (from `get-accounts` subcommand)")
|
||||
downloadCommand.Flags.StringVar(&acctType, "accttype", "CHECKING", "AcctType (from `get-accounts` subcommand)")
|
||||
}
|
||||
|
||||
func downloadCheckFlags() bool {
|
||||
ret := checkServerFlags()
|
||||
|
||||
if len(filename) == 0 {
|
||||
fmt.Println("Error: Filename empty")
|
||||
return false
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func download() {
|
||||
client, query := NewRequest()
|
||||
|
||||
uid, err := ofxgo.RandomUID()
|
||||
if err != nil {
|
||||
fmt.Println("Error creating uid for transaction:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
statementRequest := ofxgo.StatementRequest{
|
||||
TrnUID: *uid,
|
||||
BankAcctFrom: ofxgo.BankAcct{
|
||||
BankId: ofxgo.String(bankId),
|
||||
AcctId: ofxgo.String(acctId),
|
||||
AcctType: ofxgo.String(acctType),
|
||||
},
|
||||
DtStart: ofxgo.Date(time.Unix(0, 0)),
|
||||
DtEnd: ofxgo.Date(time.Now()),
|
||||
Include: true,
|
||||
}
|
||||
query.Banking = append(query.Banking, &statementRequest)
|
||||
|
||||
response, err := client.RequestNoParse(query)
|
||||
if err != nil {
|
||||
fmt.Println("Error requesting account statement:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating file to write to:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, response.Body)
|
||||
if err != nil {
|
||||
fmt.Println("Error writing response to file:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
93
cmd/ofx/banktransactions.go
Normal file
93
cmd/ofx/banktransactions.go
Normal file
@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/aclindsa/ofxgo"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var bankTransactionsCommand = Command{
|
||||
Name: "transactions-bank",
|
||||
Description: "Print bank transactions and balance",
|
||||
Flags: flag.NewFlagSet("transactions-bank", flag.ExitOnError),
|
||||
CheckFlags: checkServerFlags,
|
||||
Do: bankTransactions,
|
||||
}
|
||||
|
||||
func init() {
|
||||
defineServerFlags(bankTransactionsCommand.Flags)
|
||||
bankTransactionsCommand.Flags.StringVar(&bankId, "bankid", "", "BankId (from `get-accounts` subcommand)")
|
||||
bankTransactionsCommand.Flags.StringVar(&acctId, "acctid", "", "AcctId (from `get-accounts` subcommand)")
|
||||
bankTransactionsCommand.Flags.StringVar(&acctType, "accttype", "CHECKING", "AcctType (from `get-accounts` subcommand)")
|
||||
}
|
||||
|
||||
func bankTransactions() {
|
||||
client, query := NewRequest()
|
||||
|
||||
uid, err := ofxgo.RandomUID()
|
||||
if err != nil {
|
||||
fmt.Println("Error creating uid for transaction:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
statementRequest := ofxgo.StatementRequest{
|
||||
TrnUID: *uid,
|
||||
BankAcctFrom: ofxgo.BankAcct{
|
||||
BankId: ofxgo.String(bankId),
|
||||
AcctId: ofxgo.String(acctId),
|
||||
AcctType: ofxgo.String(acctType),
|
||||
},
|
||||
DtStart: ofxgo.Date(time.Unix(0, 0)),
|
||||
DtEnd: ofxgo.Date(time.Now()),
|
||||
Include: true,
|
||||
}
|
||||
query.Banking = append(query.Banking, &statementRequest)
|
||||
|
||||
response, err := client.Request(query)
|
||||
if err != nil {
|
||||
fmt.Println("Error requesting account statement:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if response.Signon.Status.Code != 0 {
|
||||
fmt.Printf("Nonzero signon status with message: %s\n", response.Signon.Status.Message)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(response.Banking) < 1 {
|
||||
fmt.Println("No banking messages received")
|
||||
return
|
||||
}
|
||||
|
||||
if stmt, ok := response.Banking[0].(ofxgo.StatementResponse); ok {
|
||||
fmt.Printf("Balance: %s %s (as of %s)\n", stmt.BalAmt, stmt.CurDef, stmt.DtAsOf)
|
||||
fmt.Println("Transactions:")
|
||||
for _, tran := range stmt.BankTranList.Transactions {
|
||||
printTransaction(stmt.CurDef, &tran)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printTransaction(defCurrency ofxgo.String, tran *ofxgo.Transaction) {
|
||||
currency := defCurrency
|
||||
if len(tran.Currency) > 0 {
|
||||
currency = tran.Currency
|
||||
} else if len(tran.OrigCurrency) > 0 {
|
||||
currency = tran.Currency
|
||||
}
|
||||
|
||||
var name string
|
||||
if len(tran.Name) > 0 {
|
||||
name = string(tran.Name)
|
||||
} else {
|
||||
name = string(tran.Payee.Name)
|
||||
}
|
||||
|
||||
if len(tran.Memo) > 0 {
|
||||
name = name + " - " + string(tran.Memo)
|
||||
}
|
||||
|
||||
fmt.Printf("%s %-15s %-11s %s\n", tran.DtPosted, tran.TrnAmt.String()+" "+string(currency), tran.TrnType, name)
|
||||
}
|
77
cmd/ofx/ccdownload.go
Normal file
77
cmd/ofx/ccdownload.go
Normal file
@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/aclindsa/ofxgo"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ccDownloadCommand = Command{
|
||||
Name: "download-cc",
|
||||
Description: "Download a credit card account statement to a file",
|
||||
Flags: flag.NewFlagSet("download-cc", flag.ExitOnError),
|
||||
CheckFlags: ccDownloadCheckFlags,
|
||||
Do: ccDownload,
|
||||
}
|
||||
|
||||
func init() {
|
||||
defineServerFlags(ccDownloadCommand.Flags)
|
||||
ccDownloadCommand.Flags.StringVar(&filename, "filename", "./download.ofx", "The file to save to")
|
||||
ccDownloadCommand.Flags.StringVar(&acctId, "acctid", "", "AcctId (from `get-accounts` subcommand)")
|
||||
}
|
||||
|
||||
func ccDownloadCheckFlags() bool {
|
||||
ret := checkServerFlags()
|
||||
|
||||
if len(filename) == 0 {
|
||||
fmt.Println("Error: Filename empty")
|
||||
return false
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func ccDownload() {
|
||||
client, query := NewRequest()
|
||||
|
||||
uid, err := ofxgo.RandomUID()
|
||||
if err != nil {
|
||||
fmt.Println("Error creating uid for transaction:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
statementRequest := ofxgo.CCStatementRequest{
|
||||
TrnUID: *uid,
|
||||
CCAcctFrom: ofxgo.CCAcct{
|
||||
AcctId: ofxgo.String(acctId),
|
||||
},
|
||||
DtStart: ofxgo.Date(time.Unix(0, 0)),
|
||||
DtEnd: ofxgo.Date(time.Now()),
|
||||
Include: true,
|
||||
}
|
||||
query.CreditCards = append(query.CreditCards, &statementRequest)
|
||||
|
||||
response, err := client.RequestNoParse(query)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error requesting account statement:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating file to write to:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, response.Body)
|
||||
if err != nil {
|
||||
fmt.Println("Error writing response to file:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
85
cmd/ofx/cctransactions.go
Normal file
85
cmd/ofx/cctransactions.go
Normal file
@ -0,0 +1,85 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/aclindsa/ofxgo"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ccTransactionsCommand = Command{
|
||||
Name: "transactions-cc",
|
||||
Description: "Print credit card transactions and balance",
|
||||
Flags: flag.NewFlagSet("transactions-cc", flag.ExitOnError),
|
||||
CheckFlags: checkServerFlags,
|
||||
Do: ccTransactions,
|
||||
}
|
||||
|
||||
func init() {
|
||||
defineServerFlags(ccTransactionsCommand.Flags)
|
||||
ccTransactionsCommand.Flags.StringVar(&acctId, "acctid", "", "AcctId (from `get-accounts` subcommand)")
|
||||
}
|
||||
|
||||
func ccTransactions() {
|
||||
client, query := NewRequest()
|
||||
|
||||
uid, err := ofxgo.RandomUID()
|
||||
if err != nil {
|
||||
fmt.Println("Error creating uid for transaction:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
statementRequest := ofxgo.CCStatementRequest{
|
||||
TrnUID: *uid,
|
||||
CCAcctFrom: ofxgo.CCAcct{
|
||||
AcctId: ofxgo.String(acctId),
|
||||
},
|
||||
DtStart: ofxgo.Date(time.Unix(0, 0)),
|
||||
DtEnd: ofxgo.Date(time.Now()),
|
||||
Include: true,
|
||||
}
|
||||
query.CreditCards = append(query.CreditCards, &statementRequest)
|
||||
|
||||
response, err := client.Request(query)
|
||||
if err != nil {
|
||||
fmt.Println("Error requesting account statement:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if response.Signon.Status.Code != 0 {
|
||||
fmt.Printf("Nonzero signon status with message: %s\n", response.Signon.Status.Message)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(response.CreditCards) < 1 {
|
||||
fmt.Println("No banking messages received")
|
||||
return
|
||||
}
|
||||
|
||||
if stmt, ok := response.CreditCards[0].(ofxgo.CCStatementResponse); ok {
|
||||
fmt.Printf("Balance: %s %s (as of %s)\n", stmt.BalAmt, stmt.CurDef, stmt.DtAsOf)
|
||||
fmt.Println("Transactions:")
|
||||
for _, tran := range stmt.BankTranList.Transactions {
|
||||
currency := stmt.CurDef
|
||||
if len(tran.Currency) > 0 {
|
||||
currency = tran.Currency
|
||||
} else if len(tran.OrigCurrency) > 0 {
|
||||
currency = tran.Currency
|
||||
}
|
||||
|
||||
var name string
|
||||
if len(tran.Name) > 0 {
|
||||
name = string(tran.Name)
|
||||
} else {
|
||||
name = string(tran.Payee.Name)
|
||||
}
|
||||
|
||||
if len(tran.Memo) > 0 {
|
||||
name = name + " - " + string(tran.Memo)
|
||||
}
|
||||
|
||||
fmt.Printf("%s %-15s %-11s %s\n", tran.DtPosted, tran.TrnAmt.String()+" "+string(currency), tran.TrnType, name)
|
||||
}
|
||||
}
|
||||
}
|
61
cmd/ofx/command.go
Normal file
61
cmd/ofx/command.go
Normal file
@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/howeyc/gopass"
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
Name string
|
||||
Description string
|
||||
Flags *flag.FlagSet
|
||||
CheckFlags func() bool // Check the flag values after they're parsed, printing errors and returning false if they're incorrect
|
||||
Do func() // Run the command (only called if CheckFlags returns true)
|
||||
}
|
||||
|
||||
func (c *Command) Usage() {
|
||||
fmt.Printf("Usage of %s:\n", c.Name)
|
||||
c.Flags.PrintDefaults()
|
||||
}
|
||||
|
||||
// flags common to all server transactions
|
||||
var serverURL, username, password, org, fid, appId, appVer, ofxVersion, clientUID string
|
||||
var noIndentRequests bool
|
||||
|
||||
func defineServerFlags(f *flag.FlagSet) {
|
||||
f.StringVar(&serverURL, "url", "", "Financial institution's OFX Server URL (see ofxhome.com if you don't know it)")
|
||||
f.StringVar(&username, "username", "", "Your username at financial institution")
|
||||
f.StringVar(&password, "password", "", "Your password at financial institution")
|
||||
f.StringVar(&org, "org", "", "'ORG' for your financial institution")
|
||||
f.StringVar(&fid, "fid", "", "'FID' for your financial institution")
|
||||
f.StringVar(&appId, "appid", "QWIN", "'APPID' to pretend to be")
|
||||
f.StringVar(&appVer, "appver", "2400", "'APPVER' to pretend to be")
|
||||
f.StringVar(&ofxVersion, "ofxversion", "203", "OFX version to use")
|
||||
f.StringVar(&clientUID, "clientuid", "", "Client UID (only required by a few FIs, like Chase)")
|
||||
f.BoolVar(&noIndentRequests, "noindent", false, "Don't indent OFX requests")
|
||||
}
|
||||
|
||||
func checkServerFlags() bool {
|
||||
var ret bool = true
|
||||
if len(serverURL) == 0 {
|
||||
fmt.Println("Error: Server URL empty")
|
||||
ret = false
|
||||
}
|
||||
if len(username) == 0 {
|
||||
fmt.Println("Error: Username empty")
|
||||
ret = false
|
||||
}
|
||||
|
||||
if ret && len(password) == 0 {
|
||||
fmt.Printf("Password for %s: ", username)
|
||||
pass, err := gopass.GetPasswd()
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading password: %s\n", err)
|
||||
ret = false
|
||||
} else {
|
||||
password = string(pass)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
70
cmd/ofx/get_accounts.go
Normal file
70
cmd/ofx/get_accounts.go
Normal file
@ -0,0 +1,70 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/aclindsa/ofxgo"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var getAccountsCommand = Command{
|
||||
Name: "get-accounts",
|
||||
Description: "List accounts at your financial institution",
|
||||
Flags: flag.NewFlagSet("get-accounts", flag.ExitOnError),
|
||||
CheckFlags: checkServerFlags,
|
||||
Do: getAccounts,
|
||||
}
|
||||
|
||||
func init() {
|
||||
defineServerFlags(getAccountsCommand.Flags)
|
||||
}
|
||||
|
||||
func getAccounts() {
|
||||
client, query := NewRequest()
|
||||
|
||||
uid, err := ofxgo.RandomUID()
|
||||
if err != nil {
|
||||
fmt.Println("Error creating uid for transaction:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
acctInfo := ofxgo.AcctInfoRequest{
|
||||
TrnUID: *uid,
|
||||
DtAcctUp: ofxgo.Date(time.Unix(0, 0)),
|
||||
CltCookie: 1,
|
||||
}
|
||||
query.Signup = append(query.Signup, &acctInfo)
|
||||
|
||||
signupResponse, err := client.Request(query)
|
||||
if err != nil {
|
||||
fmt.Println("Error requesting account information:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if signupResponse.Signon.Status.Code != 0 {
|
||||
fmt.Printf("Nonzero signon status with message: %s\n", signupResponse.Signon.Status.Message)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(signupResponse.Signup) < 1 {
|
||||
fmt.Println("No signup messages received")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\nFound the following accounts:\n")
|
||||
|
||||
if acctinfo, ok := signupResponse.Signup[0].(ofxgo.AcctInfoResponse); ok {
|
||||
for _, acct := range acctinfo.AcctInfo {
|
||||
if acct.BankAcctInfo != nil {
|
||||
fmt.Printf("Bank Account:\n\tBankId: \"%s\"\n\tAcctId: \"%s\"\n\tAcctType: %s\n", acct.BankAcctInfo.BankAcctFrom.BankId, acct.BankAcctInfo.BankAcctFrom.AcctId, acct.BankAcctInfo.BankAcctFrom.AcctType)
|
||||
} else if acct.CCAcctInfo != nil {
|
||||
fmt.Printf("Credit card:\n\tAcctId: \"%s\"\n", acct.CCAcctInfo.CCAcctFrom.AcctId)
|
||||
} else if acct.InvAcctInfo != nil {
|
||||
fmt.Printf("Investment account:\n\tBrokerId: \"%s\"\n\tAcctId: \"%s\"\n", acct.InvAcctInfo.InvAcctFrom.BrokerId, acct.InvAcctInfo.InvAcctFrom.AcctId)
|
||||
} else {
|
||||
fmt.Printf("Unknown type: %s %s\n", acct.Name, acct.Desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
cmd/ofx/invdownload.go
Normal file
87
cmd/ofx/invdownload.go
Normal file
@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/aclindsa/ofxgo"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var invDownloadCommand = Command{
|
||||
Name: "download-inv",
|
||||
Description: "Download a investment account statement to a file",
|
||||
Flags: flag.NewFlagSet("download-inv", flag.ExitOnError),
|
||||
CheckFlags: invDownloadCheckFlags,
|
||||
Do: invDownload,
|
||||
}
|
||||
|
||||
var brokerId string
|
||||
|
||||
func init() {
|
||||
defineServerFlags(invDownloadCommand.Flags)
|
||||
invDownloadCommand.Flags.StringVar(&filename, "filename", "./download.ofx", "The file to save to")
|
||||
invDownloadCommand.Flags.StringVar(&acctId, "acctid", "", "AcctId (from `get-accounts` subcommand)")
|
||||
invDownloadCommand.Flags.StringVar(&brokerId, "brokerid", "", "BrokerId (from `get-accounts` subcommand)")
|
||||
}
|
||||
|
||||
func invDownloadCheckFlags() bool {
|
||||
ret := checkServerFlags()
|
||||
|
||||
if len(filename) == 0 {
|
||||
fmt.Println("Error: Filename empty")
|
||||
return false
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func invDownload() {
|
||||
client, query := NewRequest()
|
||||
|
||||
uid, err := ofxgo.RandomUID()
|
||||
if err != nil {
|
||||
fmt.Println("Error creating uid for transaction:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
statementRequest := ofxgo.InvStatementRequest{
|
||||
TrnUID: *uid,
|
||||
InvAcctFrom: ofxgo.InvAcct{
|
||||
BrokerId: ofxgo.String(brokerId),
|
||||
AcctId: ofxgo.String(acctId),
|
||||
},
|
||||
DtStart: ofxgo.Date(time.Unix(0, 0)),
|
||||
DtEnd: ofxgo.Date(time.Now()),
|
||||
Include: true,
|
||||
IncludeOO: true,
|
||||
IncludePos: true,
|
||||
IncludeBalance: true,
|
||||
Include401K: true,
|
||||
Include401KBal: true,
|
||||
}
|
||||
query.Investments = append(query.Investments, &statementRequest)
|
||||
|
||||
response, err := client.RequestNoParse(query)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("Error requesting account statement:", err)
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error creating file to write to:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
_, err = io.Copy(file, response.Body)
|
||||
if err != nil {
|
||||
fmt.Println("Error writing response to file:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
208
cmd/ofx/invtransactions.go
Normal file
208
cmd/ofx/invtransactions.go
Normal file
@ -0,0 +1,208 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/aclindsa/ofxgo"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var invTransactionsCommand = Command{
|
||||
Name: "transactions-inv",
|
||||
Description: "Print investment transactions",
|
||||
Flags: flag.NewFlagSet("transactions-inv", flag.ExitOnError),
|
||||
CheckFlags: checkServerFlags,
|
||||
Do: invTransactions,
|
||||
}
|
||||
|
||||
func init() {
|
||||
defineServerFlags(invTransactionsCommand.Flags)
|
||||
invTransactionsCommand.Flags.StringVar(&acctId, "acctid", "", "AcctId (from `get-accounts` subcommand)")
|
||||
invTransactionsCommand.Flags.StringVar(&brokerId, "brokerid", "", "BrokerId (from `get-accounts` subcommand)")
|
||||
}
|
||||
|
||||
func invTransactions() {
|
||||
client, query := NewRequest()
|
||||
|
||||
uid, err := ofxgo.RandomUID()
|
||||
if err != nil {
|
||||
fmt.Println("Error creating uid for transaction:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
statementRequest := ofxgo.InvStatementRequest{
|
||||
TrnUID: *uid,
|
||||
InvAcctFrom: ofxgo.InvAcct{
|
||||
BrokerId: ofxgo.String(brokerId),
|
||||
AcctId: ofxgo.String(acctId),
|
||||
},
|
||||
DtStart: ofxgo.Date(time.Unix(0, 0)),
|
||||
DtEnd: ofxgo.Date(time.Now()),
|
||||
Include: true,
|
||||
IncludeBalance: true,
|
||||
// TODO Include401K: true,
|
||||
// TODO Include401KBal: true,
|
||||
}
|
||||
query.Investments = append(query.Investments, &statementRequest)
|
||||
|
||||
response, err := client.Request(query)
|
||||
if err != nil {
|
||||
fmt.Println("Error requesting account statement:", err)
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if response.Signon.Status.Code != 0 {
|
||||
fmt.Printf("Nonzero signon status with message: %s\n", response.Signon.Status.Message)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(response.Investments) < 1 {
|
||||
fmt.Println("No investment messages received")
|
||||
return
|
||||
}
|
||||
|
||||
if stmt, ok := response.Investments[0].(ofxgo.InvStatementResponse); ok {
|
||||
availCash := big.Rat(stmt.InvBal.AvailCash)
|
||||
if availCash.IsInt() && availCash.Num().Int64() != 0 {
|
||||
fmt.Printf("Balance: %s %s (as of %s)\n", stmt.InvBal.AvailCash, stmt.CurDef, stmt.DtAsOf)
|
||||
}
|
||||
for _, banktrans := range stmt.InvTranList.BankTransactions {
|
||||
fmt.Printf("\nBank Transactions for %s subaccount:\n", banktrans.SubAcctFund)
|
||||
for _, tran := range banktrans.Transactions {
|
||||
printTransaction(stmt.CurDef, &tran)
|
||||
}
|
||||
}
|
||||
fmt.Printf("\nInvestment Transactions:\n")
|
||||
for _, t := range stmt.InvTranList.InvTransactions {
|
||||
fmt.Printf("%-14s", t.TransactionType())
|
||||
switch tran := t.(type) {
|
||||
case ofxgo.BuyDebt:
|
||||
printInvBuy(stmt.CurDef, &tran.InvBuy)
|
||||
case ofxgo.BuyMF:
|
||||
printInvBuy(stmt.CurDef, &tran.InvBuy)
|
||||
case ofxgo.BuyOpt:
|
||||
printInvBuy(stmt.CurDef, &tran.InvBuy)
|
||||
case ofxgo.BuyOther:
|
||||
printInvBuy(stmt.CurDef, &tran.InvBuy)
|
||||
case ofxgo.BuyStock:
|
||||
printInvBuy(stmt.CurDef, &tran.InvBuy)
|
||||
case ofxgo.ClosureOpt:
|
||||
printInvTran(&tran.InvTran)
|
||||
fmt.Println("%s %s contracts (%s shares each)\n", tran.OptAction, tran.Units, tran, tran.ShPerCtrct)
|
||||
case ofxgo.Income:
|
||||
printInvTran(&tran.InvTran)
|
||||
currency := stmt.CurDef
|
||||
if len(tran.Currency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
} else if len(tran.OrigCurrency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
}
|
||||
fmt.Printf(" %s %s %s (%s %s)\n", tran.IncomeType, tran.Total, currency, tran.SecId.UniqueIdType, tran.SecId.UniqueId)
|
||||
// TODO print ticker instead of CUSIP
|
||||
case ofxgo.InvExpense:
|
||||
printInvTran(&tran.InvTran)
|
||||
currency := stmt.CurDef
|
||||
if len(tran.Currency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
} else if len(tran.OrigCurrency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
}
|
||||
fmt.Printf(" %s %s (%s %s)\n", tran.Total, currency, tran.SecId.UniqueIdType, tran.SecId.UniqueId)
|
||||
// TODO print ticker instead of CUSIP
|
||||
case ofxgo.JrnlFund:
|
||||
printInvTran(&tran.InvTran)
|
||||
fmt.Printf(" %s %s (%s -> %s)\n", tran.Total, stmt.CurDef, tran.SubAcctFrom, tran.SubAcctTo)
|
||||
case ofxgo.JrnlSec:
|
||||
printInvTran(&tran.InvTran)
|
||||
fmt.Printf(" %s %s %s (%s -> %s)\n", tran.Units, tran.SecId.UniqueIdType, tran.SecId.UniqueId, tran.SubAcctFrom, tran.SubAcctTo)
|
||||
// TODO print ticker instead of CUSIP
|
||||
case ofxgo.MarginInterest:
|
||||
printInvTran(&tran.InvTran)
|
||||
currency := stmt.CurDef
|
||||
if len(tran.Currency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
} else if len(tran.OrigCurrency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
}
|
||||
fmt.Printf(" %s %s\n", tran.Total, currency)
|
||||
case ofxgo.Reinvest:
|
||||
printInvTran(&tran.InvTran)
|
||||
currency := stmt.CurDef
|
||||
if len(tran.Currency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
} else if len(tran.OrigCurrency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
}
|
||||
fmt.Printf(" %s (%s %s)@%s %s (Total: %s)\n", tran.Units, tran.SecId.UniqueIdType, tran.SecId.UniqueId, tran.UnitPrice, currency, tran.Total)
|
||||
// TODO print ticker instead of CUSIP
|
||||
case ofxgo.RetOfCap:
|
||||
printInvTran(&tran.InvTran)
|
||||
currency := stmt.CurDef
|
||||
if len(tran.Currency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
} else if len(tran.OrigCurrency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
}
|
||||
fmt.Printf(" %s %s (%s %s)\n", tran.Total, currency, tran.SecId.UniqueIdType, tran.SecId.UniqueId)
|
||||
// TODO print ticker instead of CUSIP
|
||||
case ofxgo.SellDebt:
|
||||
printInvSell(stmt.CurDef, &tran.InvSell)
|
||||
case ofxgo.SellMF:
|
||||
printInvSell(stmt.CurDef, &tran.InvSell)
|
||||
case ofxgo.SellOpt:
|
||||
printInvSell(stmt.CurDef, &tran.InvSell)
|
||||
case ofxgo.SellOther:
|
||||
printInvSell(stmt.CurDef, &tran.InvSell)
|
||||
case ofxgo.SellStock:
|
||||
printInvSell(stmt.CurDef, &tran.InvSell)
|
||||
case ofxgo.Split:
|
||||
printInvTran(&tran.InvTran)
|
||||
currency := stmt.CurDef
|
||||
if len(tran.Currency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
} else if len(tran.OrigCurrency.CurSym) > 0 {
|
||||
currency = tran.Currency.CurSym
|
||||
}
|
||||
fmt.Printf(" %s/%s %s -> %s shares of %s %s (%s %s for fractional shares)\n", tran.Numerator, tran.Denominator, tran.OldUnits, tran.NewUnits, tran.SecId.UniqueIdType, tran.SecId.UniqueId, tran.FracCash, currency)
|
||||
// TODO print ticker instead of CUSIP
|
||||
case ofxgo.Transfer:
|
||||
printInvTran(&tran.InvTran)
|
||||
fmt.Printf(" %s (%s %s) %s\n", tran.Units, tran.SecId.UniqueIdType, tran.SecId.UniqueId, tran.TferAction)
|
||||
// TODO print ticker instead of CUSIP
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printInvTran(it *ofxgo.InvTran) {
|
||||
fmt.Printf("%s", it.DtTrade)
|
||||
}
|
||||
|
||||
func printInvBuy(defCurrency ofxgo.String, ib *ofxgo.InvBuy) {
|
||||
printInvTran(&ib.InvTran)
|
||||
currency := defCurrency
|
||||
if len(ib.Currency.CurSym) > 0 {
|
||||
currency = ib.Currency.CurSym
|
||||
} else if len(ib.OrigCurrency.CurSym) > 0 {
|
||||
currency = ib.Currency.CurSym
|
||||
}
|
||||
|
||||
fmt.Printf("%s (%s %s)@%s %s (Total: %s)\n", ib.Units, ib.SecId.UniqueIdType, ib.SecId.UniqueId, ib.UnitPrice, currency, ib.Total)
|
||||
// TODO print ticker instead of CUSIP
|
||||
}
|
||||
|
||||
func printInvSell(defCurrency ofxgo.String, is *ofxgo.InvSell) {
|
||||
printInvTran(&is.InvTran)
|
||||
currency := defCurrency
|
||||
if len(is.Currency.CurSym) > 0 {
|
||||
currency = is.Currency.CurSym
|
||||
} else if len(is.OrigCurrency.CurSym) > 0 {
|
||||
currency = is.Currency.CurSym
|
||||
}
|
||||
|
||||
fmt.Printf(" %s (%s %s)@%s %s (Total: %s)\n", is.Units, is.SecId.UniqueIdType, is.SecId.UniqueId, is.UnitPrice, currency, is.Total)
|
||||
// TODO print ticker instead of CUSIP
|
||||
}
|
80
cmd/ofx/main.go
Normal file
80
cmd/ofx/main.go
Normal file
@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var commands = []Command{
|
||||
getAccountsCommand,
|
||||
downloadCommand,
|
||||
ccDownloadCommand,
|
||||
invDownloadCommand,
|
||||
bankTransactionsCommand,
|
||||
ccTransactionsCommand,
|
||||
invTransactionsCommand,
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Println(`The ofxgo command-line client provides a simple interface to
|
||||
query, parse, and display financial data via the OFX specification.
|
||||
|
||||
Usage:
|
||||
ofx command [arguments]
|
||||
|
||||
The commands are:`)
|
||||
|
||||
maxlen := 0
|
||||
for _, cmd := range commands {
|
||||
if len(cmd.Name) > maxlen {
|
||||
maxlen = len(cmd.Name)
|
||||
}
|
||||
}
|
||||
formatString := " %-" + strconv.Itoa(maxlen) + "s %s\n"
|
||||
|
||||
for _, cmd := range commands {
|
||||
fmt.Printf(formatString, cmd.Name, cmd.Description)
|
||||
}
|
||||
}
|
||||
|
||||
func runCmd(c *Command) {
|
||||
err := c.Flags.Parse(os.Args[2:])
|
||||
if err != nil {
|
||||
fmt.Printf("Error parsing flags: %s\n", err)
|
||||
c.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if !c.CheckFlags() {
|
||||
fmt.Println()
|
||||
c.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
c.Do()
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("Error: Please supply a sub-command. Usage:\n")
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
cmdName := os.Args[1]
|
||||
for _, cmd := range commands {
|
||||
if cmd.Name == cmdName {
|
||||
runCmd(&cmd)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
switch cmdName {
|
||||
case "-h", "-help", "--help", "help":
|
||||
usage()
|
||||
default:
|
||||
fmt.Println("Error: Invalid sub-command. Usage:")
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
24
cmd/ofx/util.go
Normal file
24
cmd/ofx/util.go
Normal file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/aclindsa/ofxgo"
|
||||
)
|
||||
|
||||
func NewRequest() (*ofxgo.Client, *ofxgo.Request) {
|
||||
var client = ofxgo.Client{
|
||||
AppId: appId,
|
||||
AppVer: appVer,
|
||||
SpecVersion: ofxVersion,
|
||||
NoIndent: noIndentRequests,
|
||||
}
|
||||
|
||||
var query ofxgo.Request
|
||||
query.URL = serverURL
|
||||
query.Signon.ClientUID = ofxgo.UID(clientUID)
|
||||
query.Signon.UserId = ofxgo.String(username)
|
||||
query.Signon.UserPass = ofxgo.String(password)
|
||||
query.Signon.Org = ofxgo.String(org)
|
||||
query.Signon.Fid = ofxgo.String(fid)
|
||||
|
||||
return &client, &query
|
||||
}
|
Loading…
Reference in New Issue
Block a user