package main import ( "bytes" "errors" "fmt" "io/ioutil" "local/oauth2" "net/http" "net/url" "os" "path" "regexp" "strconv" "strings" "time" yaml "gopkg.in/yaml.v2" ) func Upload(config Config, transaction *Transaction) error { switch config.Uploader { case UploaderTodo: return uploadTodo(config, transaction) case UploaderLedger: return uploadLedger(config, transaction) case UploaderPTTodo: return uploadPTTodo(config, transaction) default: return errors.New("not impl: uploader") } } func uploadTodo(config Config, transaction *Transaction) error { params := url.Values{ "list": {config.TodoList}, "title": {transaction.Format()}, "tag": {config.TodoTag}, } req, err := http.NewRequest("POST", config.TodoAddr+"/ajax.php?newTask", strings.NewReader(params.Encode())) if err != nil { return err } req.Header.Set("Cookie", oauth2.COOKIE+"="+config.TodoToken) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { b, _ := ioutil.ReadAll(resp.Body) return fmt.Errorf("bad status from todo: %v: %s", resp.StatusCode, b) } return nil } func uploadPTTodo(config Config, transaction *Transaction) error { b, err := ioutil.ReadFile(config.TodoAddr) if os.IsNotExist(err) { b = []byte("todo:\n") } else if err != nil { return err } else if len(b) == 0 { b = []byte("todo:\n") } f, err := ioutil.TempFile(os.TempDir(), path.Base(config.TodoAddr)) if err != nil { return err } defer f.Close() sep := []byte{'\n'} seek := []byte("todo:") for len(b) > 0 { idx := bytes.Index(b, sep) if idx == -1 { idx = len(b) - 1 } fmt.Fprintf(f, "%s\n", b[:idx]) if bytes.Equal(bytes.TrimSpace(b[:idx]), seek) { fmt.Fprintf(f, `- {"todo":%q, "tags":%q}%s`, transaction.Format(), config.TodoTag, "\n") } b = b[idx+1:] } f.Close() var v interface{} if b, err := ioutil.ReadFile(f.Name()); err != nil { return err } else if err := yaml.Unmarshal(b, &v); err != nil { return err } return os.Rename(f.Name(), config.TodoAddr) } func uploadLedger(config Config, transaction *Transaction) error { f, err := os.OpenFile(config.TodoAddr, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return err } defer f.Close() amount, _ := strconv.ParseFloat(transaction.Amount, 32) amount *= -1 remote := "Withdrawal:" if amount > 0 { remote = "Deposit:" } regexp := regexp.MustCompile(`[0-9a-zA-Z]`) for _, substr := range regexp.FindAllString(transaction.Vendor, -1) { remote += substr } fmt.Fprintf(f, "%-50s%-s\n", formatGMailDate(transaction.Date), transaction.Vendor) fmt.Fprintf(f, "%-50s%-50s$%.2f\n", "", "AssetAccount:"+transaction.Bank.String()+":"+transaction.Account, amount) fmt.Fprintf(f, "%-50s%-s\n", "", remote) return nil } func formatGMailDate(s string) string { for _, format := range []string{ "[Mon, 2 Jan 2006 15:04:05 -0700 (MST)]", "[Mon, 2 Jan 2006 15:04:05 -0700]", } { time, err := time.Parse(format, s) if err == nil { return time.Format("2006-01-02") } } return s }