Skip to content

Instantly share code, notes, and snippets.

@kristianjaeger
Created January 7, 2022 00:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kristianjaeger/95baab28507dc3358f095e6518785d95 to your computer and use it in GitHub Desktop.
Save kristianjaeger/95baab28507dc3358f095e6518785d95 to your computer and use it in GitHub Desktop.
Simple example of grabbing stock quote data from a text file and then sending an email
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"net/smtp"
"os"
"strconv"
"strings"
"github.com/kelseyhightower/envconfig"
)
/*
Environment variables should be in the format of:
QEMAIL_FROM
QEMAIL_PASSWORD
QEMAIL_PURCHASEPRICE
QEMAIL_TO
QEMAIL_SMTPHOST (example: smtp.gmail.com)
QEMAIL_SMTPPORT (example: 587)
QEMAIL_QUOTEFILEANDPATH
QEMAIL_TICKER (example: aapl)
*/
type ConfigEntries struct {
From string
Password string
PurchasePrice float64
To string
SmtpHost string
SmtpPort string
QuoteFileAndPath string
Ticker string
}
/*
main - simple example of grabbing stock quote data from a text file and then sending an email.
notes: This was developed using go1.15.8 windows/amd64 (should work on other platforms), as noted above this uses
envconfig for environment variable config entries (please see their github for how to get started), in my local testing I
used gmail to send the emails and outlook.live.com to receive (should work with others as well for send or receive),
qtrn was used to get the stock data - https://github.com/piquette/qtrn . Also, there could be more effecient / effective ways
to get and send this data. This was mainly an exercise for me to get more practice with golang (so this isn't really production
ready code. :) ). Please feel free to use for your own educational purposes.
*/
func main() {
var c ConfigEntries
err := envconfig.Process("qemail", &c)
if err != nil {
log.Fatal(err.Error())
}
// can be used for validating environment variables
/* _, err = fmt.Printf(c.From, c.Password, c.PurchasePrice, c.To, c.SmtpHost, c.SmtpPort, c.QuoteFileAndPath, c.Ticker)
if err != nil {
log.Fatal(err.Error())
} */
// sender data
from := c.From
password := c.Password
// recepient email address
to := []string{
c.To,
}
// smtp server configuration.
smtpHost := c.SmtpHost
smtpPort := c.SmtpPort
// get purchase price from env var
purchasePrice := c.PurchasePrice
// this assumes there's a scheduled task / cron that calls qtrn quote <myTicker> and pipes to <myTicker>Quote.txt for example
quoteFileNameAndPath := c.QuoteFileAndPath
quoteData := *ReadQuoteDataFromFile(quoteFileNameAndPath)
text := string(quoteData)
lastPriceStr := GetLastPriceString(text)
// calculate difference between purchase price and current price
var lastPrice float64
lastPrice, err = strconv.ParseFloat(*lastPriceStr, 64)
if err != nil {
log.Fatal(err.Error())
}
var gainOrLoss float64 = float64(((lastPrice - purchasePrice) / purchasePrice) * 100)
// Clean up and convert the quote data to basic html
quoteData = *CleanAndFormatQuoteData(quoteData)
// construct the message
subject := "Subject: " + c.Ticker + " quote\r\n"
subjectBytes := []byte(subject)
mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
mimeBytes := []byte(mime)
message := append(subjectBytes, mimeBytes...)
message = append(message, quoteData...)
gainOrLossBytes := []byte("<br/><br/><b>Gain or Loss: " + fmt.Sprintf("%.2f", gainOrLoss) + "%</b>")
message = append(message, gainOrLossBytes...)
purchasePriceBytes := []byte("<br/><br/>Purchase price: " + fmt.Sprintf("%.2f", purchasePrice))
message = append(message, purchasePriceBytes...)
// setup authentication
auth := smtp.PlainAuth("", from, password, smtpHost)
// send the email
err = smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, message)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Email Sent Successfully!")
}
/*
GetLastPriceString gets last price from the string content in the file
Example:
Checking line: Last
Checking line: |
Checking line: 384.29
*/
func GetLastPriceString(FileContent string) *string {
textWithoutWhitespace := strings.Fields(FileContent)
loopCounter := 0
indexOfLast := 0
lastPriceStr := ""
for _, line := range textWithoutWhitespace {
//fmt.Println("Checking line:", line)
if strings.Contains(line, "Last") {
indexOfLast = loopCounter
}
if loopCounter == indexOfLast+2 {
lastPriceStr = line
}
loopCounter++
}
return &lastPriceStr
}
/*
CleanAndFormatQuoteData cleans and formats quote data. There are cleaner ways to approach this but it works at least.
*/
func CleanAndFormatQuoteData(QuoteDataBytes []byte) *[]byte {
QuoteDataBytes = bytes.Replace(QuoteDataBytes, []byte("["), []byte(""), -1)
QuoteDataBytes = bytes.Replace(QuoteDataBytes, []byte("1m"), []byte("<b>"), -1)
QuoteDataBytes = bytes.Replace(QuoteDataBytes, []byte("0m"), []byte("</b>"), -1)
QuoteDataBytes = bytes.Replace(QuoteDataBytes, []byte(" "), []byte(""), -1)
QuoteDataBytes = bytes.Replace(QuoteDataBytes, []byte("|"), []byte("<br/>"), -1)
QuoteDataBytes = bytes.Replace(QuoteDataBytes, []byte("*------------*-----------------------------*"), []byte("<hr/>"), -1)
return &QuoteDataBytes
}
/*
ReadQuoteDataFromFile reads quote data from a text file that was created by piping quote data from qtrn to the file
*/
func ReadQuoteDataFromFile(QuoteFileNameAndPath string) *[]byte {
file, err := os.Open(QuoteFileNameAndPath)
if err != nil {
log.Fatal(err)
}
defer func() {
if err = file.Close(); err != nil {
log.Fatal(err)
}
}()
quoteData, err := ioutil.ReadAll(file)
return &quoteData
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment