mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
New communications package
Support for Slack, SMSGlobal, SMTP and Telegram Supersedes: https://github.com/thrasher-/gocryptotrader/pull/126
This commit is contained in:
committed by
Adrian Gallagher
parent
d34fc9aae8
commit
9d0616d8cf
166
communications/base/base.go
Normal file
166
communications/base/base.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
|
||||
)
|
||||
|
||||
//global vars contain staged update data that will be sent to the communication
|
||||
// mediums
|
||||
var (
|
||||
TickerStaged map[string]map[string]map[string]ticker.Price
|
||||
OrderbookStaged map[string]map[string]map[string]Orderbook
|
||||
PortfolioStaged Portfolio
|
||||
SettingsStaged Settings
|
||||
ServiceStarted time.Time
|
||||
m sync.Mutex
|
||||
)
|
||||
|
||||
// Orderbook holds the minimal orderbook details to be sent to a communication
|
||||
// medium
|
||||
type Orderbook struct {
|
||||
CurrencyPair string
|
||||
AssetType string
|
||||
TotalAsks float64
|
||||
TotalBids float64
|
||||
LastUpdated string
|
||||
}
|
||||
|
||||
// Ticker holds the minimal orderbook details to be sent to a communication
|
||||
// medium
|
||||
type Ticker struct {
|
||||
CurrencyPair string
|
||||
LastUpdated string
|
||||
}
|
||||
|
||||
// Portfolio holds the minimal portfolio details to be sent to a communication
|
||||
// medium
|
||||
type Portfolio struct {
|
||||
ProfitLoss string
|
||||
}
|
||||
|
||||
// Settings holds the minimal setting details to be sent to a communication
|
||||
// medium
|
||||
type Settings struct {
|
||||
EnabledExchanges string
|
||||
EnabledCommunications string
|
||||
}
|
||||
|
||||
// Base enforces standard variables across communication packages
|
||||
type Base struct {
|
||||
Name string
|
||||
Enabled bool
|
||||
Verbose bool
|
||||
Connected bool
|
||||
}
|
||||
|
||||
// Event is a generalise event type
|
||||
type Event struct {
|
||||
Type string
|
||||
GainLoss string
|
||||
TradeDetails string
|
||||
}
|
||||
|
||||
// IsEnabled returns if the comms package has been enabled in the configuration
|
||||
func (b *Base) IsEnabled() bool {
|
||||
return b.Enabled
|
||||
}
|
||||
|
||||
// IsConnected returns if the package is connected to a server and/or ready to
|
||||
// send
|
||||
func (b *Base) IsConnected() bool {
|
||||
return b.Connected
|
||||
}
|
||||
|
||||
// GetName returns a package name
|
||||
func (b *Base) GetName() string {
|
||||
return b.Name
|
||||
}
|
||||
|
||||
// GetTicker returns staged ticker data
|
||||
func (b *Base) GetTicker(exchangeName string) string {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
tickerPrice, ok := TickerStaged[exchangeName]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
var tickerPrices []ticker.Price
|
||||
for _, x := range tickerPrice {
|
||||
for _, y := range x {
|
||||
tickerPrices = append(tickerPrices, y)
|
||||
}
|
||||
}
|
||||
|
||||
var packagedTickers []string
|
||||
for i := range tickerPrices {
|
||||
packagedTickers = append(packagedTickers, fmt.Sprintf(
|
||||
"Currency Pair: %s Ask: %f, Bid: %f High: %f Last: %f Low: %f ATH: %f Volume: %f",
|
||||
tickerPrices[i].CurrencyPair,
|
||||
tickerPrices[i].Ask,
|
||||
tickerPrices[i].Bid,
|
||||
tickerPrices[i].High,
|
||||
tickerPrices[i].Last,
|
||||
tickerPrices[i].Low,
|
||||
tickerPrices[i].PriceATH,
|
||||
tickerPrices[i].Volume))
|
||||
}
|
||||
return common.JoinStrings(packagedTickers, "\n")
|
||||
}
|
||||
|
||||
// GetOrderbook returns staged orderbook data
|
||||
func (b *Base) GetOrderbook(exchangeName string) string {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
orderbook, ok := OrderbookStaged[exchangeName]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
var orderbooks []Orderbook
|
||||
for _, x := range orderbook {
|
||||
for _, y := range x {
|
||||
orderbooks = append(orderbooks, y)
|
||||
}
|
||||
}
|
||||
|
||||
var packagedOrderbooks []string
|
||||
for i := range orderbooks {
|
||||
packagedOrderbooks = append(packagedOrderbooks, fmt.Sprintf(
|
||||
"Currency Pair: %s AssetType: %s, LastUpdated: %s TotalAsks: %f TotalBids: %f",
|
||||
orderbooks[i].CurrencyPair,
|
||||
orderbooks[i].AssetType,
|
||||
orderbooks[i].LastUpdated,
|
||||
orderbooks[i].TotalAsks,
|
||||
orderbooks[i].TotalBids))
|
||||
}
|
||||
return common.JoinStrings(packagedOrderbooks, "\n")
|
||||
}
|
||||
|
||||
// GetPortfolio returns staged portfolio info
|
||||
func (b *Base) GetPortfolio() string {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return fmt.Sprintf("%v", PortfolioStaged)
|
||||
}
|
||||
|
||||
// GetSettings returns stage setting info
|
||||
func (b *Base) GetSettings() string {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return fmt.Sprintf("%v", SettingsStaged)
|
||||
}
|
||||
|
||||
// GetStatus returns status data
|
||||
func (b *Base) GetStatus() string {
|
||||
return `
|
||||
GoCryptoTrader Service: Online
|
||||
Service Started: ` + ServiceStarted.String()
|
||||
}
|
||||
108
communications/base/base_interface.go
Normal file
108
communications/base/base_interface.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/orderbook"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
|
||||
)
|
||||
|
||||
// IComm is the main interface array across the communication packages
|
||||
type IComm []ICommunicate
|
||||
|
||||
// ICommunicate enforces standard functions across communication packages
|
||||
type ICommunicate interface {
|
||||
Setup(config config.CommunicationsConfig)
|
||||
Connect() error
|
||||
PushEvent(Event) error
|
||||
IsEnabled() bool
|
||||
IsConnected() bool
|
||||
GetName() string
|
||||
}
|
||||
|
||||
// Setup sets up communication variables and intiates a connection to the
|
||||
// communication mediums
|
||||
func (c IComm) Setup() {
|
||||
TickerStaged = make(map[string]map[string]map[string]ticker.Price)
|
||||
OrderbookStaged = make(map[string]map[string]map[string]Orderbook)
|
||||
ServiceStarted = time.Now()
|
||||
|
||||
for i := range c {
|
||||
if c[i].IsEnabled() && !c[i].IsConnected() {
|
||||
err := c[i].Connect()
|
||||
if err != nil {
|
||||
log.Printf("Communications: %s failed to connect. Err: %s", c[i].GetName(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PushEvent pushes triggered events to all enabled communication links
|
||||
func (c IComm) PushEvent(event Event) {
|
||||
for i := range c {
|
||||
if c[i].IsEnabled() && c[i].IsConnected() {
|
||||
err := c[i].PushEvent(event)
|
||||
if err != nil {
|
||||
log.Printf("Communications error - PushEvent() in package %s with %v",
|
||||
c[i].GetName(), event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetEnabledCommunicationMediums prints out enabled and connected communication
|
||||
// packages
|
||||
func (c IComm) GetEnabledCommunicationMediums() {
|
||||
var count int
|
||||
for i := range c {
|
||||
if c[i].IsEnabled() && c[i].IsConnected() {
|
||||
log.Printf("Communications: Medium %s is enabled.", c[i].GetName())
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count == 0 {
|
||||
log.Println("Communications: No communication mediums are enabled.")
|
||||
}
|
||||
}
|
||||
|
||||
// StageTickerData stages updated ticker data for the communications package
|
||||
func (c IComm) StageTickerData(exchangeName, assetType string, tickerPrice ticker.Price) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if _, ok := TickerStaged[exchangeName]; !ok {
|
||||
TickerStaged[exchangeName] = make(map[string]map[string]ticker.Price)
|
||||
}
|
||||
|
||||
if _, ok := TickerStaged[exchangeName][assetType]; !ok {
|
||||
TickerStaged[exchangeName][assetType] = make(map[string]ticker.Price)
|
||||
}
|
||||
|
||||
TickerStaged[exchangeName][assetType][tickerPrice.CurrencyPair] = tickerPrice
|
||||
}
|
||||
|
||||
// StageOrderbookData stages updated orderbook data for the communications
|
||||
// package
|
||||
func (c IComm) StageOrderbookData(exchangeName, assetType string, orderbook orderbook.Base) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
if _, ok := OrderbookStaged[exchangeName]; !ok {
|
||||
OrderbookStaged[exchangeName] = make(map[string]map[string]Orderbook)
|
||||
}
|
||||
|
||||
if _, ok := OrderbookStaged[exchangeName][assetType]; !ok {
|
||||
OrderbookStaged[exchangeName][assetType] = make(map[string]Orderbook)
|
||||
}
|
||||
|
||||
_, totalAsks := orderbook.CalculateTotalAsks()
|
||||
_, totalBids := orderbook.CalculateTotalBids()
|
||||
|
||||
OrderbookStaged[exchangeName][assetType][orderbook.CurrencyPair] = Orderbook{
|
||||
CurrencyPair: orderbook.CurrencyPair,
|
||||
TotalAsks: totalAsks,
|
||||
TotalBids: totalBids,
|
||||
LastUpdated: orderbook.LastUpdated.String()}
|
||||
}
|
||||
84
communications/base/base_test.go
Normal file
84
communications/base/base_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
b Base
|
||||
i IComm
|
||||
)
|
||||
|
||||
func TestStart(t *testing.T) {
|
||||
b = Base{
|
||||
Name: "test",
|
||||
Enabled: true,
|
||||
Verbose: true,
|
||||
Connected: true,
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEnabled(t *testing.T) {
|
||||
if !b.IsEnabled() {
|
||||
t.Error("test failed - base IsEnabled() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsConnected(t *testing.T) {
|
||||
if !b.IsConnected() {
|
||||
t.Error("test failed - base IsConnected() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetName(t *testing.T) {
|
||||
if b.GetName() != "test" {
|
||||
t.Error("test failed - base GetName() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTicker(t *testing.T) {
|
||||
v := b.GetTicker("ANX")
|
||||
if v != "" {
|
||||
t.Error("test failed - base GetTicker() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrderbook(t *testing.T) {
|
||||
v := b.GetOrderbook("ANX")
|
||||
if v != "" {
|
||||
t.Error("test failed - base GetOrderbook() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPortfolio(t *testing.T) {
|
||||
v := b.GetPortfolio()
|
||||
if v != "{}" {
|
||||
t.Error("test failed - base GetPortfolio() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSettings(t *testing.T) {
|
||||
v := b.GetSettings()
|
||||
if v != "{ }" {
|
||||
t.Error("test failed - base GetSettings() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStatus(t *testing.T) {
|
||||
v := b.GetStatus()
|
||||
if v == "" {
|
||||
t.Error("test failed - base GetStatus() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
i.Setup()
|
||||
}
|
||||
|
||||
func TestPushEvent(t *testing.T) {
|
||||
i.PushEvent(Event{})
|
||||
}
|
||||
|
||||
func TestGetEnabledCommunicationMediums(t *testing.T) {
|
||||
i.GetEnabledCommunicationMediums()
|
||||
}
|
||||
47
communications/communications.go
Normal file
47
communications/communications.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package communications
|
||||
|
||||
import (
|
||||
"github.com/thrasher-/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-/gocryptotrader/communications/slack"
|
||||
"github.com/thrasher-/gocryptotrader/communications/smsglobal"
|
||||
"github.com/thrasher-/gocryptotrader/communications/smtpservice"
|
||||
"github.com/thrasher-/gocryptotrader/communications/telegram"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
)
|
||||
|
||||
// Communications is the overarching type across the communications packages
|
||||
type Communications struct {
|
||||
base.IComm
|
||||
}
|
||||
|
||||
// NewComm sets up and returns a pointer to a Communications object
|
||||
func NewComm(config config.CommunicationsConfig) *Communications {
|
||||
var comm Communications
|
||||
|
||||
if config.TelegramConfig.Enabled {
|
||||
Telegram := new(telegram.Telegram)
|
||||
Telegram.Setup(config)
|
||||
comm.IComm = append(comm.IComm, Telegram)
|
||||
}
|
||||
|
||||
if config.SMSGlobalConfig.Enabled {
|
||||
SMSGlobal := new(smsglobal.SMSGlobal)
|
||||
SMSGlobal.Setup(config)
|
||||
comm.IComm = append(comm.IComm, SMSGlobal)
|
||||
}
|
||||
|
||||
if config.SMTPConfig.Enabled {
|
||||
SMTP := new(smtpservice.SMTPservice)
|
||||
SMTP.Setup(config)
|
||||
comm.IComm = append(comm.IComm, SMTP)
|
||||
}
|
||||
|
||||
if config.SlackConfig.Enabled {
|
||||
Slack := new(slack.Slack)
|
||||
Slack.Setup(config)
|
||||
comm.IComm = append(comm.IComm, Slack)
|
||||
}
|
||||
|
||||
comm.Setup()
|
||||
return &comm
|
||||
}
|
||||
373
communications/slack/slack.go
Normal file
373
communications/slack/slack.go
Normal file
@@ -0,0 +1,373 @@
|
||||
// Package slack is used to connect to the slack network. Slack is a
|
||||
// code-centric collaboration hub that allows users to connect via an app and
|
||||
// share different types of data
|
||||
package slack
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
)
|
||||
|
||||
// const declares main slack url and commands that will be supported on client
|
||||
// side
|
||||
const (
|
||||
SlackURL = "https://slack.com/api/rtm.start"
|
||||
|
||||
cmdStatus = "!status"
|
||||
cmdHelp = "!help"
|
||||
cmdSettings = "!settings"
|
||||
cmdTicker = "!ticker"
|
||||
cmdPortfolio = "!portfolio"
|
||||
cmdOrderbook = "!orderbook"
|
||||
|
||||
getHelp = `GoCryptoTrader SlackBot, thank you for using this service!
|
||||
Current commands are:
|
||||
!status - Displays current working status of bot
|
||||
!help - Displays help text
|
||||
!settings - Displays current settings
|
||||
!ticker - Displays recent ANX ticker
|
||||
!portfolio - Displays portfolio data
|
||||
!orderbook - Displays current ANX orderbook`
|
||||
)
|
||||
|
||||
// Slack starts a websocket connection and uses https://api.slack.com/rtm real
|
||||
// time messaging
|
||||
type Slack struct {
|
||||
base.Base
|
||||
|
||||
TargetChannel string
|
||||
VerificationToken string
|
||||
|
||||
TargetChannelID string
|
||||
Details Response
|
||||
ReconnectURL string
|
||||
WebsocketConn *websocket.Conn
|
||||
Connected bool
|
||||
Shutdown bool
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// Setup takes in a slack configuration, sets bots target channel and
|
||||
// sets verfication token to access workspace
|
||||
func (s *Slack) Setup(config config.CommunicationsConfig) {
|
||||
s.Name = config.SlackConfig.Name
|
||||
s.Enabled = config.SlackConfig.Enabled
|
||||
s.Verbose = config.SlackConfig.Verbose
|
||||
s.TargetChannel = config.SlackConfig.TargetChannel
|
||||
s.VerificationToken = config.SlackConfig.VerificationToken
|
||||
}
|
||||
|
||||
// Connect connects to the service
|
||||
func (s *Slack) Connect() error {
|
||||
if err := s.NewConnection(); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Connected = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushEvent pushes an event to either a slack channel or specific client
|
||||
func (s *Slack) PushEvent(base.Event) error {
|
||||
return errors.New("not yet implemented")
|
||||
}
|
||||
|
||||
// BuildURL returns an appended token string with the SlackURL
|
||||
func (s *Slack) BuildURL(token string) string {
|
||||
return fmt.Sprintf("%s?token=%s", SlackURL, token)
|
||||
}
|
||||
|
||||
// GetChannelsString returns a list of all channels on the slack workspace
|
||||
func (s *Slack) GetChannelsString() []string {
|
||||
var channels []string
|
||||
for i := range s.Details.Channels {
|
||||
channels = append(channels, s.Details.Channels[i].NameNormalized)
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
// GetUsernameByID returns a users name by ID
|
||||
func (s *Slack) GetUsernameByID(ID string) string {
|
||||
for i := range s.Details.Users {
|
||||
if s.Details.Users[i].ID == ID {
|
||||
return s.Details.Users[i].Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetIDByName returns either a group ID or Channel ID
|
||||
func (s *Slack) GetIDByName(userName string) (string, error) {
|
||||
id, err := s.GetGroupIDByName(userName)
|
||||
if err != nil {
|
||||
return s.GetChannelIDByName(userName)
|
||||
}
|
||||
return id, err
|
||||
}
|
||||
|
||||
// GetGroupIDByName returns a groupID by group name
|
||||
func (s *Slack) GetGroupIDByName(group string) (string, error) {
|
||||
for i := range s.Details.Groups {
|
||||
if s.Details.Groups[i].Name == group {
|
||||
return s.Details.Groups[i].ID, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("Channel not found")
|
||||
}
|
||||
|
||||
// GetChannelIDByName returns a channel ID by its corresponding name
|
||||
func (s *Slack) GetChannelIDByName(channel string) (string, error) {
|
||||
for i := range s.Details.Channels {
|
||||
if s.Details.Channels[i].Name == channel {
|
||||
return s.Details.Channels[i].ID, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("Channel not found")
|
||||
}
|
||||
|
||||
// GetUsersInGroup returns a list of users currently in a group
|
||||
func (s *Slack) GetUsersInGroup(group string) []string {
|
||||
for i := range s.Details.Groups {
|
||||
if s.Details.Groups[i].Name == group {
|
||||
return s.Details.Groups[i].Members
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewConnection connects the bot to a slack workgroup using a verification
|
||||
// token and a channel
|
||||
func (s *Slack) NewConnection() error {
|
||||
err := s.SendHTTPGetRequestSlack(s.BuildURL(s.VerificationToken), true, &s.Details)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !s.Details.Ok {
|
||||
return errors.New(s.Details.Error)
|
||||
}
|
||||
|
||||
if s.Verbose {
|
||||
log.Printf("%s [%s] connected to %s [%s] \nWebsocket URL: %s.\n",
|
||||
s.Details.Self.Name,
|
||||
s.Details.Self.ID,
|
||||
s.Details.Team.Domain,
|
||||
s.Details.Team.ID,
|
||||
s.Details.URL)
|
||||
log.Printf("Slack channels: %s", s.GetChannelsString())
|
||||
}
|
||||
|
||||
s.TargetChannelID, err = s.GetIDByName(s.TargetChannel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.WebsocketConnect()
|
||||
}
|
||||
|
||||
// WebsocketConnect creates a websocket dialer amd initiates a websocket
|
||||
// connection
|
||||
func (s *Slack) WebsocketConnect() error {
|
||||
var Dialer websocket.Dialer
|
||||
var err error
|
||||
|
||||
websocketURL := s.Details.URL
|
||||
if s.ReconnectURL != "" {
|
||||
websocketURL = s.ReconnectURL
|
||||
}
|
||||
|
||||
s.WebsocketConn, _, err = Dialer.Dial(websocketURL, http.Header{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go s.WebsocketReader()
|
||||
return nil
|
||||
}
|
||||
|
||||
// WebsocketReader reads incoming events from the websocket connection
|
||||
func (s *Slack) WebsocketReader() {
|
||||
for {
|
||||
_, resp, err := s.WebsocketConn.ReadMessage()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var data WebsocketResponse
|
||||
|
||||
err = common.JSONDecode(resp, &data)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch data.Type {
|
||||
|
||||
case "error":
|
||||
if data.Error.Msg == "Socket URL has expired" {
|
||||
if s.Verbose {
|
||||
log.Println("Slack websocket URL has expired.. Reconnecting")
|
||||
}
|
||||
if err = s.WebsocketConn.Close(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
s.ReconnectURL = ""
|
||||
s.Connected = false
|
||||
if err := s.NewConnection(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
case "hello":
|
||||
if s.Verbose {
|
||||
log.Println("Websocket connected successfully.")
|
||||
}
|
||||
s.Connected = true
|
||||
go s.WebsocketKeepAlive()
|
||||
|
||||
case "reconnect_url":
|
||||
type reconnectResponse struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
var recURL reconnectResponse
|
||||
err = common.JSONDecode(resp, &recURL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
s.ReconnectURL = recURL.URL
|
||||
if s.Verbose {
|
||||
log.Printf("Reconnect URL set to %s\n", s.ReconnectURL)
|
||||
}
|
||||
|
||||
case "presence_change":
|
||||
var pres PresenceChange
|
||||
err = common.JSONDecode(resp, &pres)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if s.Verbose {
|
||||
log.Printf("Presence change. User %s [%s] changed status to %s\n",
|
||||
s.GetUsernameByID(pres.User),
|
||||
pres.User, pres.Presence)
|
||||
}
|
||||
|
||||
case "message":
|
||||
if data.ReplyTo != 0 {
|
||||
continue
|
||||
}
|
||||
var msg Message
|
||||
err = common.JSONDecode(resp, &msg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if s.Verbose {
|
||||
log.Printf("Msg received by %s [%s] with text: %s\n",
|
||||
s.GetUsernameByID(msg.User),
|
||||
msg.User, msg.Text)
|
||||
}
|
||||
if string(msg.Text[0]) == "!" {
|
||||
s.HandleMessage(msg)
|
||||
}
|
||||
|
||||
case "pong":
|
||||
if s.Verbose {
|
||||
log.Println("Pong recieved from server")
|
||||
}
|
||||
default:
|
||||
log.Println(string(resp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WebsocketKeepAlive sends a ping every 5 minutes to keep connection alive
|
||||
func (s *Slack) WebsocketKeepAlive() {
|
||||
ticker := time.NewTicker(5 * time.Minute)
|
||||
|
||||
for {
|
||||
<-ticker.C
|
||||
if err := s.WebsocketSend("ping", ""); err != nil {
|
||||
log.Println("slack WebsocketKeepAlive() error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WebsocketSend sends a message via the websocket connection
|
||||
func (s *Slack) WebsocketSend(eventType, text string) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
newMessage := SendMessage{
|
||||
ID: time.Now().Unix(),
|
||||
Type: eventType,
|
||||
Channel: s.TargetChannelID,
|
||||
Text: text,
|
||||
}
|
||||
data, err := json.Marshal(newMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.WebsocketConn.WriteMessage(websocket.TextMessage, data)
|
||||
}
|
||||
|
||||
// HandleMessage handles incoming messages and/or commands from slack
|
||||
func (s Slack) HandleMessage(msg Message) {
|
||||
switch {
|
||||
case common.StringContains(msg.Text, cmdStatus):
|
||||
s.WebsocketSend("message", s.GetStatus())
|
||||
|
||||
case common.StringContains(msg.Text, cmdHelp):
|
||||
s.WebsocketSend("message", getHelp)
|
||||
|
||||
case common.StringContains(msg.Text, cmdTicker):
|
||||
s.WebsocketSend("message", s.GetTicker("ANX"))
|
||||
|
||||
case common.StringContains(msg.Text, cmdOrderbook):
|
||||
s.WebsocketSend("message", s.GetOrderbook("ANX"))
|
||||
|
||||
case common.StringContains(msg.Text, cmdSettings):
|
||||
s.WebsocketSend("message", s.GetSettings())
|
||||
|
||||
case common.StringContains(msg.Text, cmdPortfolio):
|
||||
s.WebsocketSend("message", s.GetPortfolio())
|
||||
|
||||
default:
|
||||
s.WebsocketSend("message", "GoCryptoTrader SlackBot - Command Unknown!")
|
||||
}
|
||||
}
|
||||
|
||||
// SendHTTPGetRequestSlack sends a HTTP request
|
||||
func (s *Slack) SendHTTPGetRequestSlack(url string, jsonDecode bool, result interface{}) error {
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpCode := res.StatusCode
|
||||
if httpCode != 200 && httpCode != 400 {
|
||||
log.Printf("HTTP status code: %d\n", httpCode)
|
||||
return errors.New("status code was not 200")
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if jsonDecode {
|
||||
return common.JSONDecode(contents, &result)
|
||||
}
|
||||
|
||||
result = string(contents)
|
||||
return nil
|
||||
}
|
||||
90
communications/slack/slack_test.go
Normal file
90
communications/slack/slack_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
)
|
||||
|
||||
const (
|
||||
verificationToken = ""
|
||||
)
|
||||
|
||||
var s Slack
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
cfg := config.GetConfig()
|
||||
cfg.LoadConfig(config.ConfigTestFile)
|
||||
|
||||
s.Setup(cfg.GetCommunicationsConfig())
|
||||
}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
err := s.Connect()
|
||||
if err == nil {
|
||||
t.Error("test failed - slack Connect() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushEvent(t *testing.T) {
|
||||
err := s.PushEvent(base.Event{})
|
||||
if err == nil {
|
||||
t.Error("test failed - slack PushEvent() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildURL(t *testing.T) {
|
||||
v := s.BuildURL("lol123")
|
||||
if v != "https://slack.com/api/rtm.start?token=lol123" {
|
||||
t.Error("test failed - slack BuildURL() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChannelsString(t *testing.T) {
|
||||
chans := s.GetChannelsString()
|
||||
if len(chans) != 0 {
|
||||
t.Error("test failed - slack GetChannelsString() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUsernameByID(t *testing.T) {
|
||||
username := s.GetUsernameByID("1337")
|
||||
if len(username) != 0 {
|
||||
t.Error("test failed - slack GetUsernameByID() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIDByName(t *testing.T) {
|
||||
id, err := s.GetIDByName("batman")
|
||||
if err == nil {
|
||||
t.Error("test failed - slack GetIDByName() error")
|
||||
}
|
||||
if len(id) != 0 {
|
||||
t.Error("test failed - slack GetIDByName() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetChannelIDByName(t *testing.T) {
|
||||
id, err := s.GetChannelIDByName("1337")
|
||||
if err == nil {
|
||||
t.Error("test failed - slack GetChannelIDByName() error")
|
||||
}
|
||||
if len(id) != 0 {
|
||||
t.Error("test failed - slack GetChannelIDByName() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUsersInGroup(t *testing.T) {
|
||||
username := s.GetUsersInGroup("supergroup")
|
||||
if len(username) != 0 {
|
||||
t.Error("test failed - slack GetUsersInGroup() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewConnection(t *testing.T) {
|
||||
err := s.NewConnection()
|
||||
if err == nil {
|
||||
t.Error("test failed - slack NewConnection() error")
|
||||
}
|
||||
}
|
||||
441
communications/slack/slack_types.go
Normal file
441
communications/slack/slack_types.go
Normal file
@@ -0,0 +1,441 @@
|
||||
package slack
|
||||
|
||||
// WebsocketResponse holds websocket response data
|
||||
type WebsocketResponse struct {
|
||||
Type string `json:"type"`
|
||||
ReplyTo int `json:"reply_to"`
|
||||
Error struct {
|
||||
Msg string `json:"msg"`
|
||||
Code int `json:"code"`
|
||||
} `json:"error"`
|
||||
}
|
||||
|
||||
// SendMessage holds details for message information
|
||||
type SendMessage struct {
|
||||
ID int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Channel string `json:"channel"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// Message is a response type handling message data
|
||||
type Message struct {
|
||||
Channel string `json:"channel"`
|
||||
User string `json:"user"`
|
||||
Text string `json:"text"`
|
||||
SourceTeam string `json:"source_team"`
|
||||
Timestamp float64 `json:"ts,string"`
|
||||
Team string `json:"team"`
|
||||
}
|
||||
|
||||
// PresenceChange holds user presence data
|
||||
type PresenceChange struct {
|
||||
Presence string `json:"presence"`
|
||||
User string `json:"user"`
|
||||
}
|
||||
|
||||
// Response is a generalised response type
|
||||
type Response struct {
|
||||
Bots []struct {
|
||||
AppID string `json:"app_id"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Icons struct {
|
||||
Image36 string `json:"image_36"`
|
||||
Image48 string `json:"image_48"`
|
||||
Image72 string `json:"image_72"`
|
||||
} `json:"icons"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Updated int `json:"updated"`
|
||||
} `json:"bots"`
|
||||
CacheTs int `json:"cache_ts"`
|
||||
CacheTsVersion string `json:"cache_ts_version"`
|
||||
CacheVersion string `json:"cache_version"`
|
||||
CanManageSharedChannels bool `json:"can_manage_shared_channels"`
|
||||
Channels []struct {
|
||||
Created int `json:"created"`
|
||||
Creator string `json:"creator"`
|
||||
HasPins bool `json:"has_pins"`
|
||||
ID string `json:"id"`
|
||||
IsArchived bool `json:"is_archived"`
|
||||
IsChannel bool `json:"is_channel"`
|
||||
IsGeneral bool `json:"is_general"`
|
||||
IsMember bool `json:"is_member"`
|
||||
IsOrgShared bool `json:"is_org_shared"`
|
||||
IsShared bool `json:"is_shared"`
|
||||
Name string `json:"name"`
|
||||
NameNormalized string `json:"name_normalized"`
|
||||
PreviousNames []string `json:"previous_names"`
|
||||
} `json:"channels"`
|
||||
Dnd struct {
|
||||
DndEnabled bool `json:"dnd_enabled"`
|
||||
NextDndEndTs int `json:"next_dnd_end_ts"`
|
||||
NextDndStartTs int `json:"next_dnd_start_ts"`
|
||||
SnoozeEnabled bool `json:"snooze_enabled"`
|
||||
} `json:"dnd"`
|
||||
Groups []struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
IsGroup bool `json:"is_group"`
|
||||
Created int64 `json:"created"`
|
||||
Creator string `json:"creator"`
|
||||
IsArchived bool `json:"is_archived"`
|
||||
NameNormalised string `json:"name_normalised"`
|
||||
IsMPIM bool `json:"is_mpim"`
|
||||
HasPins bool `json:"has_pins"`
|
||||
IsOpen bool `json:"is_open"`
|
||||
LastRead string `json:"last_read"`
|
||||
Members []string `json:"members"`
|
||||
Topic struct {
|
||||
Value string `json:"string"`
|
||||
Creator string `json:"string"`
|
||||
LastSet int64 `json:"last_set"`
|
||||
} `json:"topic"`
|
||||
Purpose struct {
|
||||
Value string `json:"string"`
|
||||
Creator string `json:"string"`
|
||||
LastSet int64 `json:"last_set"`
|
||||
} `json:"purpose"`
|
||||
} `json:"groups"`
|
||||
Ims []struct {
|
||||
Created int `json:"created"`
|
||||
HasPins bool `json:"has_pins"`
|
||||
ID string `json:"id"`
|
||||
IsIm bool `json:"is_im"`
|
||||
IsOpen bool `json:"is_open"`
|
||||
IsOrgShared bool `json:"is_org_shared"`
|
||||
LastRead string `json:"last_read"`
|
||||
User string `json:"user"`
|
||||
} `json:"ims"`
|
||||
LatestEventTs string `json:"latest_event_ts"`
|
||||
Ok bool `json:"ok"`
|
||||
Error string `json:"error"`
|
||||
ReadOnlyChannels []interface{} `json:"read_only_channels"`
|
||||
Self struct {
|
||||
Created int `json:"created"`
|
||||
ID string `json:"id"`
|
||||
ManualPresence string `json:"manual_presence"`
|
||||
Name string `json:"name"`
|
||||
Prefs struct {
|
||||
A11yAnimations bool `json:"a11y_animations"`
|
||||
A11yFontSize string `json:"a11y_font_size"`
|
||||
AllChannelsLoud bool `json:"all_channels_loud"`
|
||||
AllNotificationsPrefs string `json:"all_notifications_prefs"`
|
||||
AllUnreadsSortOrder string `json:"all_unreads_sort_order"`
|
||||
AllowCallsToSetCurrentStatus bool `json:"allow_calls_to_set_current_status"`
|
||||
AnalyticsUpsellCoachmarkSeen bool `json:"analytics_upsell_coachmark_seen"`
|
||||
ArrowHistory bool `json:"arrow_history"`
|
||||
AtChannelSuppressedChannels string `json:"at_channel_suppressed_channels"`
|
||||
BoxEnabled bool `json:"box_enabled"`
|
||||
ChannelSort string `json:"channel_sort"`
|
||||
ClientLogsPri string `json:"client_logs_pri"`
|
||||
ColorNamesInList bool `json:"color_names_in_list"`
|
||||
ConfirmClearAllUnreads bool `json:"confirm_clear_all_unreads"`
|
||||
ConfirmShCallStart bool `json:"confirm_sh_call_start"`
|
||||
ConfirmUserMarkedAway bool `json:"confirm_user_marked_away"`
|
||||
ConvertEmoticons bool `json:"convert_emoticons"`
|
||||
DisplayDisplayNames bool `json:"display_display_names"`
|
||||
DisplayRealNamesOverride int `json:"display_real_names_override"`
|
||||
DndEnabled bool `json:"dnd_enabled"`
|
||||
DndEndHour string `json:"dnd_end_hour"`
|
||||
DndStartHour string `json:"dnd_start_hour"`
|
||||
DropboxEnabled bool `json:"dropbox_enabled"`
|
||||
EmailAlerts string `json:"email_alerts"`
|
||||
EmailAlertsSleepUntil int `json:"email_alerts_sleep_until"`
|
||||
EmailMisc bool `json:"email_misc"`
|
||||
EmailWeekly bool `json:"email_weekly"`
|
||||
EmojiAutocompleteBig bool `json:"emoji_autocomplete_big"`
|
||||
EmojiMode string `json:"emoji_mode"`
|
||||
EmojiUse string `json:"emoji_use"`
|
||||
EnableReactEmojiPicker bool `json:"enable_react_emoji_picker"`
|
||||
EnableUnreadView bool `json:"enable_unread_view"`
|
||||
EnhancedDebugging bool `json:"enhanced_debugging"`
|
||||
EnterIsSpecialInTbt bool `json:"enter_is_special_in_tbt"`
|
||||
EnterpriseMdmCustomMsg string `json:"enterprise_mdm_custom_msg"`
|
||||
EnterpriseMigrationSeen bool `json:"enterprise_migration_seen"`
|
||||
ExpandInlineImgs bool `json:"expand_inline_imgs"`
|
||||
ExpandInternalInlineImgs bool `json:"expand_internal_inline_imgs"`
|
||||
ExpandNonMediaAttachments bool `json:"expand_non_media_attachments"`
|
||||
ExpandSnippets bool `json:"expand_snippets"`
|
||||
FKeySearch bool `json:"f_key_search"`
|
||||
FlannelServerPool string `json:"flannel_server_pool"`
|
||||
FrecencyEntJumper string `json:"frecency_ent_jumper"`
|
||||
FrecencyJumper string `json:"frecency_jumper"`
|
||||
FullTextExtracts bool `json:"full_text_extracts"`
|
||||
FullerTimestamps bool `json:"fuller_timestamps"`
|
||||
GdriveAuthed bool `json:"gdrive_authed"`
|
||||
GdriveEnabled bool `json:"gdrive_enabled"`
|
||||
GraphicEmoticons bool `json:"graphic_emoticons"`
|
||||
GrowlsEnabled bool `json:"growls_enabled"`
|
||||
GrowthMsgLimitApproachingCtaCount int `json:"growth_msg_limit_approaching_cta_count"`
|
||||
GrowthMsgLimitApproachingCtaTs int `json:"growth_msg_limit_approaching_cta_ts"`
|
||||
GrowthMsgLimitLongReachedCtaCount int `json:"growth_msg_limit_long_reached_cta_count"`
|
||||
GrowthMsgLimitLongReachedCtaLastTs int `json:"growth_msg_limit_long_reached_cta_last_ts"`
|
||||
GrowthMsgLimitReachedCtaCount int `json:"growth_msg_limit_reached_cta_count"`
|
||||
GrowthMsgLimitReachedCtaLastTs int `json:"growth_msg_limit_reached_cta_last_ts"`
|
||||
HasCreatedChannel bool `json:"has_created_channel"`
|
||||
HasInvited bool `json:"has_invited"`
|
||||
HasSearched bool `json:"has_searched"`
|
||||
HasUploaded bool `json:"has_uploaded"`
|
||||
HideHexSwatch bool `json:"hide_hex_swatch"`
|
||||
HideUserGroupInfoPane bool `json:"hide_user_group_info_pane"`
|
||||
HighlightWords string `json:"highlight_words"`
|
||||
IntroToAppsMessageSeen bool `json:"intro_to_apps_message_seen"`
|
||||
Jumbomoji bool `json:"jumbomoji"`
|
||||
KKeyOmnibox bool `json:"k_key_omnibox"`
|
||||
KKeyOmniboxAutoHideCount int `json:"k_key_omnibox_auto_hide_count"`
|
||||
LastSeenAtChannelWarning int `json:"last_seen_at_channel_warning"`
|
||||
LastSnippetType string `json:"last_snippet_type"`
|
||||
LastTosAcknowledged interface{} `json:"last_tos_acknowledged"`
|
||||
LoadLato2 bool `json:"load_lato_2"`
|
||||
Locale string `json:"locale"`
|
||||
LoudChannels string `json:"loud_channels"`
|
||||
LoudChannelsSet string `json:"loud_channels_set"`
|
||||
LsDisabled bool `json:"ls_disabled"`
|
||||
MacSsbBounce string `json:"mac_ssb_bounce"`
|
||||
MacSsbBullet bool `json:"mac_ssb_bullet"`
|
||||
MarkMsgsReadImmediately bool `json:"mark_msgs_read_immediately"`
|
||||
MeasureCSSUsage bool `json:"measure_css_usage"`
|
||||
MentionsExcludeAtChannels bool `json:"mentions_exclude_at_channels"`
|
||||
MentionsExcludeAtUserGroups bool `json:"mentions_exclude_at_user_groups"`
|
||||
MessagesTheme string `json:"messages_theme"`
|
||||
MsgPreview bool `json:"msg_preview"`
|
||||
MsgPreviewPersistent bool `json:"msg_preview_persistent"`
|
||||
MuteSounds bool `json:"mute_sounds"`
|
||||
MutedChannels string `json:"muted_channels"`
|
||||
NeverChannels string `json:"never_channels"`
|
||||
NewMsgSnd string `json:"new_msg_snd"`
|
||||
NewxpSeenLastMessage int `json:"newxp_seen_last_message"`
|
||||
NoCreatedOverlays bool `json:"no_created_overlays"`
|
||||
NoInvitesWidgetInSidebar bool `json:"no_invites_widget_in_sidebar"`
|
||||
NoJoinedOverlays bool `json:"no_joined_overlays"`
|
||||
NoMacelectronBanner bool `json:"no_macelectron_banner"`
|
||||
NoMacssb1Banner bool `json:"no_macssb1_banner"`
|
||||
NoMacssb2Banner bool `json:"no_macssb2_banner"`
|
||||
NoOmniboxInChannels bool `json:"no_omnibox_in_channels"`
|
||||
NoTextInNotifications bool `json:"no_text_in_notifications"`
|
||||
NoWinssb1Banner bool `json:"no_winssb1_banner"`
|
||||
ObeyInlineImgLimit bool `json:"obey_inline_img_limit"`
|
||||
OnboardingCancelled bool `json:"onboarding_cancelled"`
|
||||
OnboardingSlackbotConversationStep int `json:"onboarding_slackbot_conversation_step"`
|
||||
OverloadedMessageEnabled bool `json:"overloaded_message_enabled"`
|
||||
PagekeysHandled bool `json:"pagekeys_handled"`
|
||||
PostsFormattingGuide bool `json:"posts_formatting_guide"`
|
||||
PreferredSkinTone string `json:"preferred_skin_tone"`
|
||||
PrevNextBtn bool `json:"prev_next_btn"`
|
||||
PrivacyPolicySeen bool `json:"privacy_policy_seen"`
|
||||
PromptedForEmailDisabling bool `json:"prompted_for_email_disabling"`
|
||||
PushAtChannelSuppressedChannels string `json:"push_at_channel_suppressed_channels"`
|
||||
PushDmAlert bool `json:"push_dm_alert"`
|
||||
PushEverything bool `json:"push_everything"`
|
||||
PushIdleWait int `json:"push_idle_wait"`
|
||||
PushLoudChannels string `json:"push_loud_channels"`
|
||||
PushLoudChannelsSet string `json:"push_loud_channels_set"`
|
||||
PushMentionAlert bool `json:"push_mention_alert"`
|
||||
PushMentionChannels string `json:"push_mention_channels"`
|
||||
PushShowPreview bool `json:"push_show_preview"`
|
||||
PushSound string `json:"push_sound"`
|
||||
QuestsEnabled bool `json:"quests_enabled"`
|
||||
RequireAt bool `json:"require_at"`
|
||||
SearchExcludeBots bool `json:"search_exclude_bots"`
|
||||
SearchExcludeChannels string `json:"search_exclude_channels"`
|
||||
SearchOnlyCurrentTeam bool `json:"search_only_current_team"`
|
||||
SearchOnlyMyChannels bool `json:"search_only_my_channels"`
|
||||
SearchSort string `json:"search_sort"`
|
||||
SeenAppSpaceCoachmark bool `json:"seen_app_space_coachmark"`
|
||||
SeenAppSpaceTutorial bool `json:"seen_app_space_tutorial"`
|
||||
SeenCallsSsMainCoachmark bool `json:"seen_calls_ss_main_coachmark"`
|
||||
SeenCallsSsWindowCoachmark bool `json:"seen_calls_ss_window_coachmark"`
|
||||
SeenCallsVideoBetaCoachmark bool `json:"seen_calls_video_beta_coachmark"`
|
||||
SeenCallsVideoGaCoachmark bool `json:"seen_calls_video_ga_coachmark"`
|
||||
SeenCustomStatusBadge bool `json:"seen_custom_status_badge"`
|
||||
SeenCustomStatusCallout bool `json:"seen_custom_status_callout"`
|
||||
SeenDomainInviteReminder bool `json:"seen_domain_invite_reminder"`
|
||||
SeenGdriveCoachmark bool `json:"seen_gdrive_coachmark"`
|
||||
SeenGuestAdminSlackbotAnnouncement bool `json:"seen_guest_admin_slackbot_announcement"`
|
||||
SeenHighlightsArrowsCoachmark bool `json:"seen_highlights_arrows_coachmark"`
|
||||
SeenHighlightsCoachmark bool `json:"seen_highlights_coachmark"`
|
||||
SeenHighlightsWarmWelcome bool `json:"seen_highlights_warm_welcome"`
|
||||
SeenIntlChannelNamesCoachmark bool `json:"seen_intl_channel_names_coachmark"`
|
||||
SeenMemberInviteReminder bool `json:"seen_member_invite_reminder"`
|
||||
SeenOnboardingChannels bool `json:"seen_onboarding_channels"`
|
||||
SeenOnboardingDirectMessages bool `json:"seen_onboarding_direct_messages"`
|
||||
SeenOnboardingInvites bool `json:"seen_onboarding_invites"`
|
||||
SeenOnboardingPrivateGroups bool `json:"seen_onboarding_private_groups"`
|
||||
SeenOnboardingRecentMentions bool `json:"seen_onboarding_recent_mentions"`
|
||||
SeenOnboardingSearch bool `json:"seen_onboarding_search"`
|
||||
SeenOnboardingSlackbotConversation bool `json:"seen_onboarding_slackbot_conversation"`
|
||||
SeenOnboardingStarredItems bool `json:"seen_onboarding_starred_items"`
|
||||
SeenOnboardingStart bool `json:"seen_onboarding_start"`
|
||||
SeenRepliesCoachmark bool `json:"seen_replies_coachmark"`
|
||||
SeenSingleEmojiMsg bool `json:"seen_single_emoji_msg"`
|
||||
SeenSsbPrompt bool `json:"seen_ssb_prompt"`
|
||||
SeenThreadsNotificationBanner bool `json:"seen_threads_notification_banner"`
|
||||
SeenUnreadViewCoachmark bool `json:"seen_unread_view_coachmark"`
|
||||
SeenWelcome2 bool `json:"seen_welcome_2"`
|
||||
SeparatePrivateChannels bool `json:"separate_private_channels"`
|
||||
SeparateSharedChannels bool `json:"separate_shared_channels"`
|
||||
ShowAllSkinTones bool `json:"show_all_skin_tones"`
|
||||
ShowJumperScores bool `json:"show_jumper_scores"`
|
||||
ShowMemoryInstrument bool `json:"show_memory_instrument"`
|
||||
ShowTyping bool `json:"show_typing"`
|
||||
SidebarBehavior string `json:"sidebar_behavior"`
|
||||
SidebarTheme string `json:"sidebar_theme"`
|
||||
SidebarThemeCustomValues string `json:"sidebar_theme_custom_values"`
|
||||
SnippetEditorWrapLongLines bool `json:"snippet_editor_wrap_long_lines"`
|
||||
SpacesNewXpBannerDismissed bool `json:"spaces_new_xp_banner_dismissed"`
|
||||
SsEmojis bool `json:"ss_emojis"`
|
||||
SsbSpaceWindow string `json:"ssb_space_window"`
|
||||
StartScrollAtOldest bool `json:"start_scroll_at_oldest"`
|
||||
TabUIReturnSelects bool `json:"tab_ui_return_selects"`
|
||||
ThreadsEverything bool `json:"threads_everything"`
|
||||
Time24 bool `json:"time24"`
|
||||
TwoFactorAuthEnabled bool `json:"two_factor_auth_enabled"`
|
||||
TwoFactorBackupType interface{} `json:"two_factor_backup_type"`
|
||||
TwoFactorType interface{} `json:"two_factor_type"`
|
||||
Tz interface{} `json:"tz"`
|
||||
UseReactSidebar bool `json:"use_react_sidebar"`
|
||||
UserColors string `json:"user_colors"`
|
||||
WebappSpellcheck bool `json:"webapp_spellcheck"`
|
||||
WelcomeMessageHidden bool `json:"welcome_message_hidden"`
|
||||
WhatsNewRead int `json:"whats_new_read"`
|
||||
WinssbRunFromTray bool `json:"winssb_run_from_tray"`
|
||||
WinssbWindowFlashBehavior string `json:"winssb_window_flash_behavior"`
|
||||
} `json:"prefs"`
|
||||
} `json:"self"`
|
||||
Subteams struct {
|
||||
All []interface{} `json:"all"`
|
||||
Self []interface{} `json:"self"`
|
||||
} `json:"subteams"`
|
||||
Team struct {
|
||||
ApproachingMsgLimit bool `json:"approaching_msg_limit"`
|
||||
AvatarBaseURL string `json:"avatar_base_url"`
|
||||
Domain string `json:"domain"`
|
||||
EmailDomain string `json:"email_domain"`
|
||||
Icon struct {
|
||||
Image102 string `json:"image_102"`
|
||||
Image132 string `json:"image_132"`
|
||||
Image230 string `json:"image_230"`
|
||||
Image34 string `json:"image_34"`
|
||||
Image44 string `json:"image_44"`
|
||||
Image68 string `json:"image_68"`
|
||||
Image88 string `json:"image_88"`
|
||||
ImageOriginal string `json:"image_original"`
|
||||
} `json:"icon"`
|
||||
ID string `json:"id"`
|
||||
MessagesCount int `json:"messages_count"`
|
||||
MsgEditWindowMins int `json:"msg_edit_window_mins"`
|
||||
Name string `json:"name"`
|
||||
OverIntegrationsLimit bool `json:"over_integrations_limit"`
|
||||
OverStorageLimit bool `json:"over_storage_limit"`
|
||||
Plan string `json:"plan"`
|
||||
Prefs struct {
|
||||
AllowCalls bool `json:"allow_calls"`
|
||||
AllowMessageDeletion bool `json:"allow_message_deletion"`
|
||||
AllowRetentionOverride bool `json:"allow_retention_override"`
|
||||
AllowSharedChannelPermsOverride bool `json:"allow_shared_channel_perms_override"`
|
||||
AuthMode string `json:"auth_mode"`
|
||||
CallingAppName string `json:"calling_app_name"`
|
||||
ChannelHandyRxns interface{} `json:"channel_handy_rxns"`
|
||||
ComplianceExportStart int `json:"compliance_export_start"`
|
||||
CustomStatusDefaultEmoji string `json:"custom_status_default_emoji"`
|
||||
CustomStatusPresets [][]string `json:"custom_status_presets"`
|
||||
DefaultChannels []string `json:"default_channels"`
|
||||
DefaultRxns []string `json:"default_rxns"`
|
||||
DisableFileDeleting bool `json:"disable_file_deleting"`
|
||||
DisableFileEditing bool `json:"disable_file_editing"`
|
||||
DisableFileUploads string `json:"disable_file_uploads"`
|
||||
DisallowPublicFileUrls bool `json:"disallow_public_file_urls"`
|
||||
Discoverable string `json:"discoverable"`
|
||||
DisplayEmailAddresses bool `json:"display_email_addresses"`
|
||||
DisplayRealNames bool `json:"display_real_names"`
|
||||
DmRetentionDuration int `json:"dm_retention_duration"`
|
||||
DmRetentionType int `json:"dm_retention_type"`
|
||||
DndEnabled bool `json:"dnd_enabled"`
|
||||
DndEndHour string `json:"dnd_end_hour"`
|
||||
DndStartHour string `json:"dnd_start_hour"`
|
||||
EnterpriseDefaultChannels []interface{} `json:"enterprise_default_channels"`
|
||||
EnterpriseMandatoryChannels []interface{} `json:"enterprise_mandatory_channels"`
|
||||
EnterpriseMdmDateEnabled int `json:"enterprise_mdm_date_enabled"`
|
||||
EnterpriseMdmLevel int `json:"enterprise_mdm_level"`
|
||||
EnterpriseTeamCreationRequest struct {
|
||||
IsEnabled bool `json:"is_enabled"`
|
||||
} `json:"enterprise_team_creation_request"`
|
||||
FileRetentionDuration int `json:"file_retention_duration"`
|
||||
FileRetentionType int `json:"file_retention_type"`
|
||||
GdriveEnabledTeam bool `json:"gdrive_enabled_team"`
|
||||
GroupRetentionDuration int `json:"group_retention_duration"`
|
||||
GroupRetentionType int `json:"group_retention_type"`
|
||||
HideReferers bool `json:"hide_referers"`
|
||||
InvitesLimit bool `json:"invites_limit"`
|
||||
InvitesOnlyAdmins bool `json:"invites_only_admins"`
|
||||
LimitReachedTs int `json:"limit_reached_ts"`
|
||||
Locale string `json:"locale"`
|
||||
LoudChannelMentionsLimit int `json:"loud_channel_mentions_limit"`
|
||||
MsgEditWindowMins int `json:"msg_edit_window_mins"`
|
||||
RequireAtForMention bool `json:"require_at_for_mention"`
|
||||
RetentionDuration int `json:"retention_duration"`
|
||||
RetentionType int `json:"retention_type"`
|
||||
ShowJoinLeave bool `json:"show_join_leave"`
|
||||
TeamHandyRxns struct {
|
||||
List []struct {
|
||||
Name string `json:"name"`
|
||||
Title string `json:"title"`
|
||||
} `json:"list"`
|
||||
Restrict bool `json:"restrict"`
|
||||
} `json:"team_handy_rxns"`
|
||||
UsesCustomizedCustomStatusPresets bool `json:"uses_customized_custom_status_presets"`
|
||||
WarnBeforeAtChannel string `json:"warn_before_at_channel"`
|
||||
WhoCanArchiveChannels string `json:"who_can_archive_channels"`
|
||||
WhoCanAtChannel string `json:"who_can_at_channel"`
|
||||
WhoCanAtEveryone string `json:"who_can_at_everyone"`
|
||||
WhoCanChangeTeamProfile string `json:"who_can_change_team_profile"`
|
||||
WhoCanCreateChannels string `json:"who_can_create_channels"`
|
||||
WhoCanCreateDeleteUserGroups string `json:"who_can_create_delete_user_groups"`
|
||||
WhoCanCreateGroups string `json:"who_can_create_groups"`
|
||||
WhoCanCreateSharedChannels string `json:"who_can_create_shared_channels"`
|
||||
WhoCanEditUserGroups string `json:"who_can_edit_user_groups"`
|
||||
WhoCanKickChannels string `json:"who_can_kick_channels"`
|
||||
WhoCanKickGroups string `json:"who_can_kick_groups"`
|
||||
WhoCanManageGuests struct {
|
||||
Type []string `json:"type"`
|
||||
} `json:"who_can_manage_guests"`
|
||||
WhoCanManageIntegrations struct {
|
||||
Type []string `json:"type"`
|
||||
} `json:"who_can_manage_integrations"`
|
||||
WhoCanManageSharedChannels struct {
|
||||
Type []string `json:"type"`
|
||||
} `json:"who_can_manage_shared_channels"`
|
||||
WhoCanPostGeneral string `json:"who_can_post_general"`
|
||||
WhoCanPostInSharedChannels struct {
|
||||
Type []string `json:"type"`
|
||||
} `json:"who_can_post_in_shared_channels"`
|
||||
WhoHasTeamVisibility string `json:"who_has_team_visibility"`
|
||||
} `json:"prefs"`
|
||||
} `json:"team"`
|
||||
URL string `json:"url"`
|
||||
Users []struct {
|
||||
Deleted bool `json:"deleted"`
|
||||
ID string `json:"id"`
|
||||
IsBot bool `json:"is_bot"`
|
||||
Name string `json:"name"`
|
||||
Presence string `json:"presence"`
|
||||
Profile struct {
|
||||
AvatarHash string `json:"avatar_hash"`
|
||||
Email string `json:"email"`
|
||||
Fields interface{} `json:"fields"`
|
||||
FirstName string `json:"first_name"`
|
||||
Image192 string `json:"image_192"`
|
||||
Image24 string `json:"image_24"`
|
||||
Image32 string `json:"image_32"`
|
||||
Image48 string `json:"image_48"`
|
||||
Image512 string `json:"image_512"`
|
||||
Image72 string `json:"image_72"`
|
||||
LastName string `json:"last_name"`
|
||||
RealName string `json:"real_name"`
|
||||
RealNameNormalized string `json:"real_name_normalized"`
|
||||
} `json:"profile"`
|
||||
TeamID string `json:"team_id"`
|
||||
Updated int `json:"updated"`
|
||||
} `json:"users"`
|
||||
}
|
||||
179
communications/smsglobal/smsglobal.go
Normal file
179
communications/smsglobal/smsglobal.go
Normal file
@@ -0,0 +1,179 @@
|
||||
// Package smsglobal allows bulk messaging to a desired recipient list
|
||||
package smsglobal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
)
|
||||
|
||||
const (
|
||||
smsGlobalAPIURL = "https://www.smsglobal.com/http-api.php"
|
||||
)
|
||||
|
||||
var (
|
||||
errSMSNotSent = errors.New("SMSGlobal message not sent")
|
||||
errContactNotFound = errors.New("SMSGlobal error contact not found")
|
||||
)
|
||||
|
||||
// SMSGlobal is the overarching type across this package
|
||||
type SMSGlobal struct {
|
||||
base.Base
|
||||
Contacts []Contact
|
||||
Username string
|
||||
Password string
|
||||
SendFrom string
|
||||
}
|
||||
|
||||
// Setup takes in a SMSGlobal configuration, sets username, password and
|
||||
// and recipient list
|
||||
func (s *SMSGlobal) Setup(config config.CommunicationsConfig) {
|
||||
s.Name = config.SMSGlobalConfig.Name
|
||||
s.Enabled = config.SMSGlobalConfig.Enabled
|
||||
s.Verbose = config.SMSGlobalConfig.Verbose
|
||||
s.Username = config.SMSGlobalConfig.Username
|
||||
s.Password = config.SMSGlobalConfig.Password
|
||||
|
||||
var contacts []Contact
|
||||
for x := range config.SMSGlobalConfig.Contacts {
|
||||
contacts = append(contacts,
|
||||
Contact{
|
||||
Name: config.SMSGlobalConfig.Contacts[x].Name,
|
||||
Number: config.SMSGlobalConfig.Contacts[x].Number,
|
||||
Enabled: config.SMSGlobalConfig.Contacts[x].Enabled,
|
||||
},
|
||||
)
|
||||
}
|
||||
s.Contacts = contacts
|
||||
}
|
||||
|
||||
// Connect connects to the service
|
||||
func (s *SMSGlobal) Connect() error {
|
||||
s.Connected = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushEvent pushes an event to a contact list via SMS
|
||||
func (s *SMSGlobal) PushEvent(base.Event) error {
|
||||
return errors.New("not yet implemented")
|
||||
}
|
||||
|
||||
// GetEnabledContacts returns how many SMS contacts are enabled in the
|
||||
// contact list
|
||||
func (s *SMSGlobal) GetEnabledContacts() int {
|
||||
counter := 0
|
||||
for x := range s.Contacts {
|
||||
if s.Contacts[x].Enabled {
|
||||
counter++
|
||||
}
|
||||
}
|
||||
return counter
|
||||
}
|
||||
|
||||
// GetContactByNumber returns a contact with supplied number
|
||||
func (s *SMSGlobal) GetContactByNumber(number string) (Contact, error) {
|
||||
for x := range s.Contacts {
|
||||
if s.Contacts[x].Number == number {
|
||||
return s.Contacts[x], nil
|
||||
}
|
||||
}
|
||||
return Contact{}, errContactNotFound
|
||||
}
|
||||
|
||||
// GetContactByName returns a contact with supplied name
|
||||
func (s *SMSGlobal) GetContactByName(name string) (Contact, error) {
|
||||
for x := range s.Contacts {
|
||||
if common.StringToLower(s.Contacts[x].Name) == common.StringToLower(name) {
|
||||
return s.Contacts[x], nil
|
||||
}
|
||||
}
|
||||
return Contact{}, errContactNotFound
|
||||
}
|
||||
|
||||
// AddContact checks to see if a contact exists and adds them if it doesn't
|
||||
func (s *SMSGlobal) AddContact(contact Contact) error {
|
||||
if contact.Name == "" || contact.Number == "" {
|
||||
return errors.New("SMSGlobal AddContact() error - nothing to add")
|
||||
}
|
||||
|
||||
if s.ContactExists(contact) {
|
||||
return errors.New("SMSGlobal AddContact() error - contact already exists")
|
||||
}
|
||||
|
||||
s.Contacts = append(s.Contacts, contact)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContactExists checks to see if a contact exists
|
||||
func (s *SMSGlobal) ContactExists(contact Contact) bool {
|
||||
for x := range s.Contacts {
|
||||
if s.Contacts[x].Number == contact.Number && common.StringToLower(s.Contacts[x].Name) == common.StringToLower(contact.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveContact removes a contact if it exists
|
||||
func (s *SMSGlobal) RemoveContact(contact Contact) error {
|
||||
if !s.ContactExists(contact) {
|
||||
return errors.New("SMSGlobal RemoveContact() error - contact does not exist")
|
||||
}
|
||||
|
||||
for x := range s.Contacts {
|
||||
if s.Contacts[x].Name == contact.Name && s.Contacts[x].Number == contact.Number {
|
||||
s.Contacts = append(s.Contacts[:x], s.Contacts[x+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("SMSGlobal RemoveContact() error - contact already removed")
|
||||
}
|
||||
|
||||
// SendMessageToAll sends a message to all enabled contacts in cfg
|
||||
func (s *SMSGlobal) SendMessageToAll(message string) error {
|
||||
for x := range s.Contacts {
|
||||
if s.Contacts[x].Enabled {
|
||||
err := s.SendMessage(s.Contacts[x].Number, message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMessage sends a message to an individual contact
|
||||
func (s *SMSGlobal) SendMessage(to, message string) error {
|
||||
if flag.Lookup("test.v") != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := url.Values{}
|
||||
values.Set("action", "sendsms")
|
||||
values.Set("user", s.Username)
|
||||
values.Set("password", s.Password)
|
||||
values.Set("from", s.SendFrom)
|
||||
values.Set("to", to)
|
||||
values.Set("text", message)
|
||||
|
||||
headers := make(map[string]string)
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
|
||||
resp, err := common.SendHTTPRequest("POST",
|
||||
smsGlobalAPIURL,
|
||||
headers,
|
||||
strings.NewReader(values.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !common.StringContains(resp, "OK: 0; Sent queued message") {
|
||||
return errSMSNotSent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
101
communications/smsglobal/smsglobal_test.go
Normal file
101
communications/smsglobal/smsglobal_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package smsglobal
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
)
|
||||
|
||||
var s SMSGlobal
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
cfg := config.GetConfig()
|
||||
cfg.LoadConfig("../../testdata/configtest.json")
|
||||
s.Setup(cfg.GetCommunicationsConfig())
|
||||
}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
err := s.Connect()
|
||||
if err != nil {
|
||||
t.Error("test failed - SMSGlobal Connect() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushEvent(t *testing.T) {
|
||||
err := s.PushEvent(base.Event{})
|
||||
if err == nil {
|
||||
t.Error("test failed - SMSGlobal PushEvent() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnabledContacts(t *testing.T) {
|
||||
v := s.GetEnabledContacts()
|
||||
if v != 1 {
|
||||
t.Error("test failed - SMSGlobal GetEnabledContacts() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContactByNumber(t *testing.T) {
|
||||
_, err := s.GetContactByNumber("1231424")
|
||||
if err != nil {
|
||||
t.Error("test failed - SMSGlobal GetContactByNumber() error", err)
|
||||
}
|
||||
_, err = s.GetContactByNumber("basketball")
|
||||
if err == nil {
|
||||
t.Error("test failed - SMSGlobal GetContactByNumber() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContactByName(t *testing.T) {
|
||||
_, err := s.GetContactByName("StyleGherkin")
|
||||
if err != nil {
|
||||
t.Error("test failed - SMSGlobal GetContactByName() error", err)
|
||||
}
|
||||
_, err = s.GetContactByName("blah")
|
||||
if err == nil {
|
||||
t.Error("test failed - SMSGlobal GetContactByName() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddContact(t *testing.T) {
|
||||
err := s.AddContact(Contact{Name: "bra", Number: "2876", Enabled: true})
|
||||
if err != nil {
|
||||
t.Error("test failed - SMSGlobal AddContact() error", err)
|
||||
}
|
||||
log.Println(s.Contacts)
|
||||
err = s.AddContact(Contact{Name: "StyleGherkin", Number: "1231424", Enabled: true})
|
||||
if err == nil {
|
||||
t.Error("test failed - SMSGlobal AddContact() error")
|
||||
}
|
||||
err = s.AddContact(Contact{Name: "", Number: "", Enabled: true})
|
||||
if err == nil {
|
||||
t.Error("test failed - SMSGlobal AddContact() error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContact(t *testing.T) {
|
||||
err := s.RemoveContact(Contact{Name: "StyleGherkin", Number: "1231424", Enabled: true})
|
||||
if err != nil {
|
||||
t.Error("test failed - SMSGlobal RemoveContact() error", err)
|
||||
}
|
||||
err = s.RemoveContact(Contact{Name: "frieda", Number: "243453", Enabled: true})
|
||||
if err == nil {
|
||||
t.Error("test failed - SMSGlobal RemoveContact() error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendMessageToAll(t *testing.T) {
|
||||
err := s.SendMessageToAll("Hello,World!")
|
||||
if err != nil {
|
||||
t.Error("test failed - SMSGlobal SendMessageToAll() error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendMessage(t *testing.T) {
|
||||
err := s.SendMessage("1337", "Hello!")
|
||||
if err != nil {
|
||||
t.Error("test failed - SMSGlobal SendMessage() error", err)
|
||||
}
|
||||
}
|
||||
8
communications/smsglobal/smsglobal_types.go
Normal file
8
communications/smsglobal/smsglobal_types.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package smsglobal
|
||||
|
||||
// Contact struct stores information related to a SMSGlobal contact
|
||||
type Contact struct {
|
||||
Name string `json:"Name"`
|
||||
Number string `json:"Number"`
|
||||
Enabled bool `json:"Enabled"`
|
||||
}
|
||||
80
communications/smtpservice/smtpservice.go
Normal file
80
communications/smtpservice/smtpservice.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package smtpservice
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
)
|
||||
|
||||
const (
|
||||
mime = "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
|
||||
msgSMTP = "To: %s\r\nSubject: %s\r\n%s\r\n%s"
|
||||
)
|
||||
|
||||
// SMTPservice uses the net/smtp package to send emails to a recipient list
|
||||
type SMTPservice struct {
|
||||
base.Base
|
||||
Host string
|
||||
Port string
|
||||
AccountName string
|
||||
AccountPassword string
|
||||
RecipientList string
|
||||
}
|
||||
|
||||
// Setup takes in a SMTP configuration and sets SMTP server details and
|
||||
// recipient list
|
||||
func (s *SMTPservice) Setup(config config.CommunicationsConfig) {
|
||||
s.Name = config.SMTPConfig.Name
|
||||
s.Enabled = config.SMTPConfig.Enabled
|
||||
s.Verbose = config.SMTPConfig.Verbose
|
||||
s.Host = config.SMTPConfig.Host
|
||||
s.Port = config.SMTPConfig.Port
|
||||
s.AccountName = config.SMTPConfig.AccountName
|
||||
s.AccountPassword = config.SMTPConfig.AccountPassword
|
||||
s.RecipientList = config.SMTPConfig.RecipientList
|
||||
}
|
||||
|
||||
// Connect connects to service
|
||||
func (s *SMTPservice) Connect() error {
|
||||
s.Connected = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushEvent sends an event to supplied recipient list via SMTP
|
||||
func (s *SMTPservice) PushEvent(base.Event) error {
|
||||
return errors.New("not yet implemented")
|
||||
}
|
||||
|
||||
// Send sends an email template to the recipient list via your SMTP host when
|
||||
// an internal event is triggered by GoCryptoTrader
|
||||
func (s *SMTPservice) Send(subject, alert string) error {
|
||||
if subject == "" || alert == "" {
|
||||
return errors.New("STMPservice Send() please add subject and alert")
|
||||
}
|
||||
|
||||
list := common.SplitStrings(s.RecipientList, ",")
|
||||
|
||||
for i := range list {
|
||||
messageToSend := fmt.Sprintf(
|
||||
msgSMTP,
|
||||
list[i],
|
||||
subject,
|
||||
mime,
|
||||
alert)
|
||||
|
||||
err := smtp.SendMail(
|
||||
s.Host+":"+s.Port,
|
||||
smtp.PlainAuth("", s.AccountName, s.AccountPassword, s.Host),
|
||||
s.AccountName,
|
||||
[]string{list[i]},
|
||||
[]byte(messageToSend))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
41
communications/smtpservice/smtpservice_test.go
Normal file
41
communications/smtpservice/smtpservice_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package smtpservice
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
)
|
||||
|
||||
var s SMTPservice
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
cfg := config.GetConfig()
|
||||
cfg.LoadConfig("../../testdata/configtest.json")
|
||||
s.Setup(cfg.GetCommunicationsConfig())
|
||||
}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
err := s.Connect()
|
||||
if err != nil {
|
||||
t.Error("test failed - smtpservice Connect() error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushEvent(t *testing.T) {
|
||||
err := s.PushEvent(base.Event{})
|
||||
if err == nil {
|
||||
t.Error("test failed - smtpservice PushEvent() error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSend(t *testing.T) {
|
||||
err := s.Send("", "")
|
||||
if err == nil {
|
||||
t.Error("test failed - smtpservice Send() error", err)
|
||||
}
|
||||
err = s.Send("subject", "alertmessage")
|
||||
if err == nil {
|
||||
t.Error("test failed - smtpservice Send() error", err)
|
||||
}
|
||||
}
|
||||
230
communications/telegram/telegram.go
Normal file
230
communications/telegram/telegram.go
Normal file
@@ -0,0 +1,230 @@
|
||||
// Package telegram is used to connect to a cloud-based mobile and desktop
|
||||
// messaging app using the bot API defined in
|
||||
// https://core.telegram.org/bots/api#recent-changes
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
)
|
||||
|
||||
const (
|
||||
apiURL = "https://api.telegram.org/bot%s/%s"
|
||||
|
||||
methodGetMe = "getMe"
|
||||
methodGetUpdates = "getUpdates"
|
||||
methodSendMessage = "sendMessage"
|
||||
|
||||
cmdStart = "/start"
|
||||
cmdStatus = "/status"
|
||||
cmdHelp = "/help"
|
||||
cmdSettings = "/settings"
|
||||
cmdTicker = "/ticker"
|
||||
cmdPortfolio = "/portfolio"
|
||||
cmdOrders = "/orderbooks"
|
||||
|
||||
cmdHelpReply = `GoCryptoTrader TelegramBot, thank you for using this service!
|
||||
Current commands are:
|
||||
/start - Will authenticate your ID
|
||||
/status - Displays the status of the bot
|
||||
/help - Displays current command list
|
||||
/settings - Displays current bot settings
|
||||
/ticker - Displays current ANX ticker data
|
||||
/portfolio - Displays your current portfolio
|
||||
/orderbooks - Displays current orderbooks for ANX`
|
||||
|
||||
talkRoot = "GoCryptoTrader bot"
|
||||
)
|
||||
|
||||
// Telegram is the overarching type across this package
|
||||
type Telegram struct {
|
||||
base.Base
|
||||
Token string
|
||||
Offset int64
|
||||
AuthorisedClients []int64
|
||||
}
|
||||
|
||||
// Setup takes in a Telegram configuration and sets verification token
|
||||
func (t *Telegram) Setup(config config.CommunicationsConfig) {
|
||||
t.Name = config.TelegramConfig.Name
|
||||
t.Enabled = config.TelegramConfig.Enabled
|
||||
t.Token = config.TelegramConfig.VerificationToken
|
||||
t.Verbose = config.TelegramConfig.Verbose
|
||||
}
|
||||
|
||||
// Connect starts an initial connection
|
||||
func (t *Telegram) Connect() error {
|
||||
if err := t.TestConnection(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.Connected = true
|
||||
go t.PollerStart()
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushEvent sends an event to a supplied recipient list via telegram
|
||||
func (t *Telegram) PushEvent(event base.Event) error {
|
||||
for i := range t.AuthorisedClients {
|
||||
err := t.SendMessage(fmt.Sprintf("Type: %s Details: %s GainOrLoss: %s",
|
||||
event.Type, event.TradeDetails, event.GainLoss), t.AuthorisedClients[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PollerStart starts the long polling sequence
|
||||
func (t *Telegram) PollerStart() {
|
||||
t.InitialConnect()
|
||||
|
||||
for {
|
||||
resp, err := t.GetUpdates()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for i := range resp.Result {
|
||||
if resp.Result[i].UpdateID > t.Offset {
|
||||
if string(resp.Result[i].Message.Text[0]) == "/" {
|
||||
err = t.HandleMessages(resp.Result[i].Message.Text, resp.Result[i].Message.From.ID)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
t.Offset = resp.Result[i].UpdateID
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InitialConnect sets offset, and sends a welcome greeting to any associated
|
||||
// IDs
|
||||
func (t *Telegram) InitialConnect() {
|
||||
resp, err := t.GetUpdates()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if !resp.Ok {
|
||||
log.Fatal(resp.Description)
|
||||
}
|
||||
|
||||
warmWelcomeList := make(map[string]int64)
|
||||
for i := range resp.Result {
|
||||
if resp.Result[i].Message.From.ID != 0 {
|
||||
warmWelcomeList[resp.Result[i].Message.From.UserName] = resp.Result[i].Message.From.ID
|
||||
}
|
||||
}
|
||||
|
||||
for userName, ID := range warmWelcomeList {
|
||||
err = t.SendMessage(fmt.Sprintf("GoCryptoTrader bot has connected: Hello, %s!", userName), ID)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(resp.Result) == 0 {
|
||||
return
|
||||
}
|
||||
t.Offset = resp.Result[len(resp.Result)-1].UpdateID
|
||||
}
|
||||
|
||||
// HandleMessages handles incoming message from the long polling routine
|
||||
func (t *Telegram) HandleMessages(text string, chatID int64) error {
|
||||
switch {
|
||||
case common.StringContains(text, cmdHelp):
|
||||
return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, cmdHelpReply), chatID)
|
||||
|
||||
case common.StringContains(text, cmdStart):
|
||||
return t.SendMessage(fmt.Sprintf("%s: START COMMANDS HERE", talkRoot), chatID)
|
||||
|
||||
case common.StringContains(text, cmdOrders):
|
||||
return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetOrderbook("ANX")), chatID)
|
||||
|
||||
case common.StringContains(text, cmdStatus):
|
||||
return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetStatus()), chatID)
|
||||
|
||||
case common.StringContains(text, cmdTicker):
|
||||
return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetTicker("ANX")), chatID)
|
||||
|
||||
case common.StringContains(text, cmdSettings):
|
||||
return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetSettings()), chatID)
|
||||
|
||||
case common.StringContains(text, cmdPortfolio):
|
||||
return t.SendMessage(fmt.Sprintf("%s: %s", talkRoot, t.GetPortfolio()), chatID)
|
||||
|
||||
default:
|
||||
return t.SendMessage(fmt.Sprintf("command %s not recognized", text), chatID)
|
||||
}
|
||||
}
|
||||
|
||||
// GetUpdates gets new updates via a long poll connection
|
||||
func (t *Telegram) GetUpdates() (GetUpdateResponse, error) {
|
||||
var newUpdates GetUpdateResponse
|
||||
path := fmt.Sprintf(apiURL, t.Token, methodGetUpdates)
|
||||
return newUpdates, t.SendHTTPRequest(path, nil, &newUpdates)
|
||||
}
|
||||
|
||||
// TestConnection tests bot's supplied authentication token
|
||||
func (t *Telegram) TestConnection() error {
|
||||
var isConnected User
|
||||
path := fmt.Sprintf(apiURL, t.Token, methodGetMe)
|
||||
|
||||
err := t.SendHTTPRequest(path, nil, &isConnected)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isConnected.Ok {
|
||||
return errors.New(isConnected.Description)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMessage sends a message to a user by their chatID
|
||||
func (t *Telegram) SendMessage(text string, chatID int64) error {
|
||||
path := fmt.Sprintf(apiURL, t.Token, methodSendMessage)
|
||||
|
||||
messageToSend := struct {
|
||||
ChatID int64 `json:"chat_id"`
|
||||
Text string `json:"text"`
|
||||
}{
|
||||
chatID,
|
||||
text,
|
||||
}
|
||||
|
||||
json, err := common.JSONEncode(&messageToSend)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := Message{}
|
||||
err = t.SendHTTPRequest(path, json, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Ok {
|
||||
return errors.New(resp.Description)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendHTTPRequest sends an authenticated HTTP request
|
||||
func (t *Telegram) SendHTTPRequest(path string, json []byte, result interface{}) error {
|
||||
headers := make(map[string]string)
|
||||
headers["content-type"] = "application/json"
|
||||
|
||||
resp, err := common.SendHTTPRequest("POST", path, headers, bytes.NewBuffer(json))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return common.JSONDecode([]byte(resp), result)
|
||||
}
|
||||
30
communications/telegram/telegram_test.go
Normal file
30
communications/telegram/telegram_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
)
|
||||
|
||||
var T Telegram
|
||||
|
||||
func TestSetup(t *testing.T) {
|
||||
cfg := config.GetConfig()
|
||||
cfg.LoadConfig("../../testdata/configtest.json")
|
||||
T.Setup(cfg.GetCommunicationsConfig())
|
||||
}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
err := T.Connect()
|
||||
if err == nil {
|
||||
t.Error("test failed - telegram Connect() error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func PushEvent(t *testing.T) {
|
||||
err := T.PushEvent(base.Event{})
|
||||
if err != nil {
|
||||
t.Error("test failed - telegram PushEvent() error", err)
|
||||
}
|
||||
}
|
||||
94
communications/telegram/telegram_types.go
Normal file
94
communications/telegram/telegram_types.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package telegram
|
||||
|
||||
// User holds user information
|
||||
type User struct {
|
||||
Ok bool `json:"ok"`
|
||||
Description string `json:"description"`
|
||||
Result struct {
|
||||
ID int64 `json:"id"`
|
||||
IsBot bool `json:"is_bot"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
UserName string `json:"username"`
|
||||
LanguageCode string `json:"language_code"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// GetUpdateResponse represents an incoming update
|
||||
type GetUpdateResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Description string `json:"description"`
|
||||
Result []struct {
|
||||
UpdateID int64 `json:"update_id"`
|
||||
Message MessageType `json:"message"`
|
||||
EditedMessage interface{} `json:"edited_message"`
|
||||
ChannelPost interface{} `json:"channel_post"`
|
||||
EditedChannelPost interface{} `json:"edited_channel_post"`
|
||||
InlineQuery interface{} `json:"inline_query"`
|
||||
ChosenInlineResult interface{} `json:"chosen_inline_result"`
|
||||
CallbackQuery interface{} `json:"callback_query"`
|
||||
ShippingQuery interface{} `json:"shipping_query"`
|
||||
PreCheckoutQuery interface{} `json:"pre_checkout_query"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// Message holds the full message information
|
||||
type Message struct {
|
||||
Ok bool `json:"ok"`
|
||||
Description string `json:"description"`
|
||||
Result MessageType `json:"result"`
|
||||
}
|
||||
|
||||
// MessageType contains message data
|
||||
type MessageType struct {
|
||||
MessageID int64 `json:"message_id"`
|
||||
From UserType `json:"from"`
|
||||
Date int64 `json:"date"`
|
||||
Chat ChatType `json:"chat"`
|
||||
ForwardFrom UserType `json:"forward_from"`
|
||||
ForwardFromChat ChatType `json:"forward_from_chat"`
|
||||
ForwardFromMessageID int64 `json:"forward_from_message_id"`
|
||||
ForwardSignature string `json:"forward_signature"`
|
||||
ForwardDate int64 `json:"forward_date"`
|
||||
ReplyToMessage interface{} `json:"reply_to_message"`
|
||||
EditDate int64 `json:"edit_date"`
|
||||
MediaGroupID string `json:"media_group_id"`
|
||||
AuthorSignature string `json:"author_signature"`
|
||||
Text string `json:"text"`
|
||||
Entities []MessageEntityType `json:"entities"`
|
||||
CaptionEntities []MessageEntityType `json:"caption_entities"`
|
||||
}
|
||||
|
||||
// MessageEntityType contains message entity information
|
||||
type MessageEntityType struct {
|
||||
Type string `json:"type"`
|
||||
Offset int64 `json:"offset"`
|
||||
Length int64 `json:"length"`
|
||||
URL string `json:"url"`
|
||||
User UserType `json:"user"`
|
||||
}
|
||||
|
||||
// UserType contains user data
|
||||
type UserType struct {
|
||||
ID int64 `json:"id"`
|
||||
IsBot bool `json:"is_bot"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
UserName string `json:"username"`
|
||||
LanguageCode string `json:"language_code"`
|
||||
}
|
||||
|
||||
// ChatType contains chat data
|
||||
type ChatType struct {
|
||||
ID int64 `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Title string `json:"title"`
|
||||
UserName string `json:"username"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
AllAdmin bool `json:"all_members_are_administrators"`
|
||||
Description string `json:"description"`
|
||||
InviteLink string `json:"invite_link"`
|
||||
StickerSetName string `json:"sticker_set_name"`
|
||||
CanSetStickerSet bool `json:"can_set_sticker_set"`
|
||||
}
|
||||
240
config/config.go
240
config/config.go
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
|
||||
"github.com/thrasher-/gocryptotrader/currency/pair"
|
||||
"github.com/thrasher-/gocryptotrader/portfolio"
|
||||
"github.com/thrasher-/gocryptotrader/smsglobal"
|
||||
)
|
||||
|
||||
// Constants declared here are filename strings and test strings
|
||||
@@ -72,15 +71,6 @@ type WebserverConfig struct {
|
||||
WebsocketAllowInsecureOrigin bool
|
||||
}
|
||||
|
||||
// SMSGlobalConfig structure holds all the variables you need for instant
|
||||
// messaging and broadcast used by SMSGlobal
|
||||
type SMSGlobalConfig struct {
|
||||
Enabled bool
|
||||
Username string
|
||||
Password string
|
||||
Contacts []smsglobal.Contact
|
||||
}
|
||||
|
||||
// Post holds the bot configuration data
|
||||
type Post struct {
|
||||
Data Config `json:"Data"`
|
||||
@@ -95,19 +85,23 @@ type CurrencyPairFormatConfig struct {
|
||||
}
|
||||
|
||||
// Config is the overarching object that holds all the information for
|
||||
// prestart management of portfolio, SMSGlobal, webserver and enabled exchange
|
||||
// prestart management of Portfolio, Communications, Webserver and Enabled
|
||||
// Exchanges
|
||||
type Config struct {
|
||||
Name string
|
||||
EncryptConfig int
|
||||
Cryptocurrencies string `json:"Cryptocurrencies,omitempty"`
|
||||
Currency CurrencyConfig `json:"CurrencyConfig,omitempty"`
|
||||
Name string
|
||||
EncryptConfig int
|
||||
GlobalHTTPTimeout time.Duration `json:"GlobalHTTPTimeout"`
|
||||
Currency CurrencyConfig `json:"CurrencyConfig"`
|
||||
Communications CommunicationsConfig `json:"Communications"`
|
||||
Portfolio portfolio.Base `json:"PortfolioAddresses"`
|
||||
Webserver WebserverConfig `json:"Webserver"`
|
||||
Exchanges []ExchangeConfig `json:"Exchanges"`
|
||||
|
||||
// Deprecated config settings, will be removed at a future date
|
||||
CurrencyPairFormat *CurrencyPairFormatConfig `json:"CurrencyPairFormat,omitempty"`
|
||||
FiatDisplayCurrency string `json:"FiatDispayCurrency,omitempty"`
|
||||
GlobalHTTPTimeout time.Duration
|
||||
Portfolio portfolio.Base `json:"PortfolioAddresses"`
|
||||
SMS SMSGlobalConfig `json:"SMSGlobal"`
|
||||
Webserver WebserverConfig `json:"Webserver"`
|
||||
Exchanges []ExchangeConfig `json:"Exchanges"`
|
||||
Cryptocurrencies string `json:"Cryptocurrencies,omitempty"`
|
||||
SMS *SMSGlobalConfig `json:"SMSGlobal,omitempty"`
|
||||
}
|
||||
|
||||
// ExchangeConfig holds all the information needed for each enabled Exchange.
|
||||
@@ -141,11 +135,186 @@ type CurrencyConfig struct {
|
||||
FiatDisplayCurrency string
|
||||
}
|
||||
|
||||
// CommunicationsConfig holds all the information needed for each
|
||||
// enabled communication package
|
||||
type CommunicationsConfig struct {
|
||||
SlackConfig SlackConfig `json:"Slack"`
|
||||
SMSGlobalConfig SMSGlobalConfig `json:"SMSGlobal"`
|
||||
SMTPConfig SMTPConfig `json:"SMTP"`
|
||||
TelegramConfig TelegramConfig `json:"Telegram"`
|
||||
}
|
||||
|
||||
// SlackConfig holds all variables to start and run the Slack package
|
||||
type SlackConfig struct {
|
||||
Name string `json:"Name"`
|
||||
Enabled bool `json:"Enabled"`
|
||||
Verbose bool `json:"Verbose"`
|
||||
TargetChannel string `json:"TargetChannel"`
|
||||
VerificationToken string `json:"VerificationToken"`
|
||||
}
|
||||
|
||||
// SMSContact stores the SMS contact info
|
||||
type SMSContact struct {
|
||||
Name string `json:"Name"`
|
||||
Number string `json:"Number"`
|
||||
Enabled bool `json:"Enabled"`
|
||||
}
|
||||
|
||||
// SMSGlobalConfig structure holds all the variables you need for instant
|
||||
// messaging and broadcast used by SMSGlobal
|
||||
type SMSGlobalConfig struct {
|
||||
Name string `json:"Name"`
|
||||
Enabled bool `json:"Enabled"`
|
||||
Verbose bool `json:"Verbose"`
|
||||
Username string `json:"Username"`
|
||||
Password string `json:"Password"`
|
||||
Contacts []SMSContact `json:"Contacts"`
|
||||
}
|
||||
|
||||
// SMTPConfig holds all variables to start and run the SMTP package
|
||||
type SMTPConfig struct {
|
||||
Name string `json:"Name"`
|
||||
Enabled bool `json:"Enabled"`
|
||||
Verbose bool `json:"Verbose"`
|
||||
Host string `json:"Host"`
|
||||
Port string `json:"Port"`
|
||||
AccountName string `json:"AccountName"`
|
||||
AccountPassword string `json:"AccountPassword"`
|
||||
RecipientList string `json:"RecipientList"`
|
||||
}
|
||||
|
||||
// TelegramConfig holds all variables to start and run the Telegram package
|
||||
type TelegramConfig struct {
|
||||
Name string `json:"Name"`
|
||||
Enabled bool `json:"Enabled"`
|
||||
Verbose bool `json:"Verbose"`
|
||||
VerificationToken string `json:"VerificationToken"`
|
||||
}
|
||||
|
||||
// GetCurrencyConfig returns currency configurations
|
||||
func (c *Config) GetCurrencyConfig() CurrencyConfig {
|
||||
return c.Currency
|
||||
}
|
||||
|
||||
// GetCommunicationsConfig returns the communications configuration
|
||||
func (c *Config) GetCommunicationsConfig() CommunicationsConfig {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
return c.Communications
|
||||
}
|
||||
|
||||
// UpdateCommunicationsConfig sets a new updated version of a Communications
|
||||
// configuration
|
||||
func (c *Config) UpdateCommunicationsConfig(config CommunicationsConfig) {
|
||||
m.Lock()
|
||||
c.Communications = config
|
||||
m.Unlock()
|
||||
}
|
||||
|
||||
// CheckCommunicationsConfig checks to see if the variables are set correctly
|
||||
// from config.json
|
||||
func (c *Config) CheckCommunicationsConfig() error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
// If the communications config hasn't been populated, populate
|
||||
// with example settings
|
||||
|
||||
if c.Communications.SlackConfig.Name == "" {
|
||||
c.Communications.SlackConfig = SlackConfig{
|
||||
Name: "Slack",
|
||||
TargetChannel: "general",
|
||||
VerificationToken: "testtest",
|
||||
}
|
||||
}
|
||||
|
||||
if c.Communications.SMSGlobalConfig.Name == "" {
|
||||
if c.SMS.Contacts != nil {
|
||||
c.Communications.SMSGlobalConfig = SMSGlobalConfig{
|
||||
Name: "SMSGlobal",
|
||||
Enabled: c.SMS.Enabled,
|
||||
Verbose: c.SMS.Verbose,
|
||||
Username: c.SMS.Username,
|
||||
Password: c.SMS.Password,
|
||||
Contacts: c.SMS.Contacts,
|
||||
}
|
||||
// flush old SMS config
|
||||
c.SMS = nil
|
||||
} else {
|
||||
c.Communications.SMSGlobalConfig = SMSGlobalConfig{
|
||||
Name: "SMSGlobal",
|
||||
Username: "main",
|
||||
Password: "test",
|
||||
|
||||
Contacts: []SMSContact{
|
||||
SMSContact{
|
||||
Name: "bob",
|
||||
Number: "1234",
|
||||
Enabled: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if c.SMS != nil {
|
||||
// flush old SMS config
|
||||
c.SMS = nil
|
||||
}
|
||||
}
|
||||
|
||||
if c.Communications.SMTPConfig.Name == "" {
|
||||
c.Communications.SMTPConfig = SMTPConfig{
|
||||
Name: "SMTP",
|
||||
Host: "smtp.google.com",
|
||||
Port: "537",
|
||||
AccountName: "some",
|
||||
AccountPassword: "password",
|
||||
RecipientList: "lol123@gmail.com",
|
||||
}
|
||||
}
|
||||
|
||||
if c.Communications.TelegramConfig.Name == "" {
|
||||
c.Communications.TelegramConfig = TelegramConfig{
|
||||
Name: "Telegram",
|
||||
VerificationToken: "testest",
|
||||
}
|
||||
}
|
||||
|
||||
if c.Communications.SlackConfig.Name != "Slack" ||
|
||||
c.Communications.SMSGlobalConfig.Name != "SMSGlobal" ||
|
||||
c.Communications.SMTPConfig.Name != "SMTP" ||
|
||||
c.Communications.TelegramConfig.Name != "Telegram" {
|
||||
return errors.New("Communications config name/s not set correctly")
|
||||
}
|
||||
if c.Communications.SlackConfig.Enabled {
|
||||
if c.Communications.SlackConfig.TargetChannel == "" ||
|
||||
c.Communications.SlackConfig.VerificationToken == "" {
|
||||
return errors.New("Slack enabled in config but variable data not set")
|
||||
}
|
||||
}
|
||||
if c.Communications.SMSGlobalConfig.Enabled {
|
||||
if c.Communications.SMSGlobalConfig.Username == "" ||
|
||||
c.Communications.SMSGlobalConfig.Password == "" ||
|
||||
len(c.Communications.SMSGlobalConfig.Contacts) == 0 {
|
||||
return errors.New("SMSGlobal enabled in config but variable data not set")
|
||||
}
|
||||
}
|
||||
if c.Communications.SMTPConfig.Enabled {
|
||||
if c.Communications.SMTPConfig.Host == "" ||
|
||||
c.Communications.SMTPConfig.Port == "" ||
|
||||
c.Communications.SMTPConfig.AccountName == "" ||
|
||||
len(c.Communications.SMTPConfig.AccountName) == 0 {
|
||||
return errors.New("SMTP enabled in config but variable data not set")
|
||||
}
|
||||
}
|
||||
if c.Communications.TelegramConfig.Enabled {
|
||||
if c.Communications.TelegramConfig.VerificationToken == "" {
|
||||
return errors.New("Telegram enabled in config but variable data not set")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportsPair returns true or not whether the exchange supports the supplied
|
||||
// pair
|
||||
func (c *Config) SupportsPair(exchName string, p pair.CurrencyPair) (bool, error) {
|
||||
@@ -296,27 +465,6 @@ func (c *Config) UpdateExchangeConfig(e ExchangeConfig) error {
|
||||
return fmt.Errorf(ErrExchangeNotFound, e.Name)
|
||||
}
|
||||
|
||||
// CheckSMSGlobalConfigValues checks concurrent SMSGlobal configurations
|
||||
func (c *Config) CheckSMSGlobalConfigValues() error {
|
||||
if c.SMS.Username == "" || c.SMS.Username == "Username" || c.SMS.Password == "" || c.SMS.Password == "Password" {
|
||||
return errors.New(WarningSMSGlobalDefaultOrEmptyValues)
|
||||
}
|
||||
contacts := 0
|
||||
for i := range c.SMS.Contacts {
|
||||
if c.SMS.Contacts[i].Enabled {
|
||||
if c.SMS.Contacts[i].Name == "" || c.SMS.Contacts[i].Number == "" || (c.SMS.Contacts[i].Name == "Bob" && c.SMS.Contacts[i].Number == "12345") {
|
||||
log.Printf(WarningSSMSGlobalSMSContactDefaultOrEmptyValues, i)
|
||||
continue
|
||||
}
|
||||
contacts++
|
||||
}
|
||||
}
|
||||
if contacts == 0 {
|
||||
return errors.New(WarningSSMSGlobalSMSNoContacts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckExchangeConfigValues returns configuation values for all enabled
|
||||
// exchanges
|
||||
func (c *Config) CheckExchangeConfigValues() error {
|
||||
@@ -693,12 +841,8 @@ func (c *Config) CheckConfig() error {
|
||||
return fmt.Errorf(ErrCheckingConfigValues, err)
|
||||
}
|
||||
|
||||
if c.SMS.Enabled {
|
||||
err = c.CheckSMSGlobalConfigValues()
|
||||
if err != nil {
|
||||
log.Print(fmt.Errorf(ErrCheckingConfigValues, err))
|
||||
c.SMS.Enabled = false
|
||||
}
|
||||
if err = c.CheckCommunicationsConfig(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if c.Webserver.Enabled {
|
||||
@@ -744,7 +888,7 @@ func (c *Config) UpdateConfig(configPath string, newCfg Config) error {
|
||||
c.Currency = newCfg.Currency
|
||||
c.GlobalHTTPTimeout = newCfg.GlobalHTTPTimeout
|
||||
c.Portfolio = newCfg.Portfolio
|
||||
c.SMS = newCfg.SMS
|
||||
c.Communications = newCfg.Communications
|
||||
c.Webserver = newCfg.Webserver
|
||||
c.Exchanges = newCfg.Exchanges
|
||||
|
||||
|
||||
@@ -266,47 +266,6 @@ func TestUpdateExchangeConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckSMSGlobalConfigValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkSMSGlobalConfigValues := GetConfig()
|
||||
err := checkSMSGlobalConfigValues.LoadConfig(ConfigTestFile)
|
||||
if err != nil {
|
||||
t.Errorf("Test failed. checkSMSGlobalConfigValues.LoadConfig: %s", err)
|
||||
}
|
||||
err = checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues()
|
||||
if err != nil {
|
||||
t.Error(
|
||||
`Test failed. checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues: Incorrect Return Value`,
|
||||
)
|
||||
}
|
||||
|
||||
checkSMSGlobalConfigValues.SMS.Username = "Username"
|
||||
err = checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues()
|
||||
if err == nil {
|
||||
t.Error(
|
||||
"Test failed. checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues: Incorrect Return Value",
|
||||
)
|
||||
}
|
||||
|
||||
checkSMSGlobalConfigValues.SMS.Username = "1234"
|
||||
checkSMSGlobalConfigValues.SMS.Contacts[0].Name = "Bob"
|
||||
checkSMSGlobalConfigValues.SMS.Contacts[0].Number = "12345"
|
||||
err = checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues()
|
||||
if err == nil {
|
||||
t.Error(
|
||||
"Test failed. checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues: Incorrect Return Value",
|
||||
)
|
||||
}
|
||||
checkSMSGlobalConfigValues.SMS.Contacts = checkSMSGlobalConfigValues.SMS.Contacts[:0]
|
||||
err = checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues()
|
||||
if err == nil {
|
||||
t.Error(
|
||||
"Test failed. checkSMSGlobalConfigValues.CheckSMSGlobalConfigValues: Incorrect Return Value",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckExchangeConfigValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
checkExchangeConfigValues := Config{}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,368 +1,369 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
"github.com/thrasher-/gocryptotrader/currency/pair"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-/gocryptotrader/smsglobal"
|
||||
)
|
||||
|
||||
var (
|
||||
loaded = false
|
||||
)
|
||||
|
||||
func testSetup(t *testing.T) {
|
||||
if !loaded {
|
||||
cfg := config.GetConfig()
|
||||
err := cfg.LoadConfig("")
|
||||
if err != nil {
|
||||
t.Fatalf("Test failed. Failed to load config %s", err)
|
||||
}
|
||||
smsglobal.New(cfg.SMS.Username, cfg.SMS.Password, cfg.Name, cfg.SMS.Contacts)
|
||||
loaded = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddEvent(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
if err != nil && eventID != 0 {
|
||||
t.Errorf("Test Failed. AddEvent: Error, %s", err)
|
||||
}
|
||||
eventID, err = AddEvent("ANXX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
if err == nil && eventID == 0 {
|
||||
t.Error("Test Failed. AddEvent: Error, error not captured in Exchange")
|
||||
}
|
||||
eventID, err = AddEvent("ANX", "prices", ">,==", pair, "SPOT", actionTest)
|
||||
if err == nil && eventID == 0 {
|
||||
t.Error("Test Failed. AddEvent: Error, error not captured in Item")
|
||||
}
|
||||
eventID, err = AddEvent("ANX", "price", "3===D", pair, "SPOT", actionTest)
|
||||
if err == nil && eventID == 0 {
|
||||
t.Error("Test Failed. AddEvent: Error, error not captured in Condition")
|
||||
}
|
||||
eventID, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", "console_prints")
|
||||
if err == nil && eventID == 0 {
|
||||
t.Error("Test Failed. AddEvent: Error, error not captured in Action")
|
||||
}
|
||||
|
||||
if !RemoveEvent(eventID) {
|
||||
t.Error("Test Failed. RemoveEvent: Error, error removing event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveEvent(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
if err != nil && eventID != 0 {
|
||||
t.Errorf("Test Failed. RemoveEvent: Error, %s", err)
|
||||
}
|
||||
if !RemoveEvent(eventID) {
|
||||
t.Error("Test Failed. RemoveEvent: Error, error removing event")
|
||||
}
|
||||
if RemoveEvent(1234) {
|
||||
t.Error("Test Failed. RemoveEvent: Error, error removing event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEventCounter(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
if err != nil {
|
||||
t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
|
||||
}
|
||||
two, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
if err != nil {
|
||||
t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
|
||||
}
|
||||
three, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
if err != nil {
|
||||
t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
|
||||
}
|
||||
|
||||
Events[three-1].Executed = true
|
||||
|
||||
total, _ := GetEventCounter()
|
||||
if total <= 0 {
|
||||
t.Errorf("Test Failed. GetEventCounter: Total = %d", total)
|
||||
}
|
||||
if !RemoveEvent(one) {
|
||||
t.Error("Test Failed. GetEventCounter: Error, error removing event")
|
||||
}
|
||||
if !RemoveEvent(two) {
|
||||
t.Error("Test Failed. GetEventCounter: Error, error removing event")
|
||||
}
|
||||
if !RemoveEvent(three) {
|
||||
t.Error("Test Failed. GetEventCounter: Error, error removing event")
|
||||
}
|
||||
|
||||
total2, _ := GetEventCounter()
|
||||
if total2 != 0 {
|
||||
t.Errorf("Test Failed. GetEventCounter: Total = %d", total2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecuteAction(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
if err != nil {
|
||||
t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
|
||||
}
|
||||
isExecuted := Events[one].ExecuteAction()
|
||||
if !isExecuted {
|
||||
t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
}
|
||||
if !RemoveEvent(one) {
|
||||
t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
}
|
||||
|
||||
action := actionSMSNotify + "," + "ALL"
|
||||
one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action)
|
||||
if err != nil {
|
||||
t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
|
||||
}
|
||||
|
||||
isExecuted = Events[one].ExecuteAction()
|
||||
if !isExecuted {
|
||||
t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
}
|
||||
if !RemoveEvent(one) {
|
||||
t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
}
|
||||
|
||||
action = actionSMSNotify + "," + "StyleGherkin"
|
||||
one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action)
|
||||
if err != nil {
|
||||
t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
|
||||
}
|
||||
|
||||
isExecuted = Events[one].ExecuteAction()
|
||||
if !isExecuted {
|
||||
t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
}
|
||||
if !RemoveEvent(one) {
|
||||
t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
}
|
||||
// More tests when ExecuteAction is expanded
|
||||
}
|
||||
|
||||
func TestEventToString(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
if err != nil {
|
||||
t.Errorf("Test Failed. EventToString: Error, %s", err)
|
||||
}
|
||||
|
||||
eventString := Events[one].String()
|
||||
if eventString != "If the BTCUSD [SPOT] price on ANX is > == then ACTION_TEST." {
|
||||
t.Error("Test Failed. EventToString: Error, incorrect return string")
|
||||
}
|
||||
|
||||
if !RemoveEvent(one) {
|
||||
t.Error("Test Failed. EventToString: Error, error removing event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckCondition(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
// Test invalid currency pair
|
||||
newPair := pair.NewCurrencyPair("A", "B")
|
||||
one, err := AddEvent("ANX", "price", ">=,10", newPair, "SPOT", actionTest)
|
||||
if err != nil {
|
||||
t.Errorf("Test Failed. CheckCondition: Error, %s", err)
|
||||
}
|
||||
conditionBool := Events[one].CheckCondition()
|
||||
if conditionBool {
|
||||
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
}
|
||||
|
||||
// Test last price == 0
|
||||
var tickerNew ticker.Price
|
||||
tickerNew.Last = 0
|
||||
newPair = pair.NewCurrencyPair("BTC", "USD")
|
||||
ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot)
|
||||
Events[one].Pair = newPair
|
||||
conditionBool = Events[one].CheckCondition()
|
||||
if conditionBool {
|
||||
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
}
|
||||
|
||||
// Test last pricce > 0 and conditional logic
|
||||
tickerNew.Last = 11
|
||||
ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot)
|
||||
Events[one].Condition = ">,10"
|
||||
conditionBool = Events[one].CheckCondition()
|
||||
if !conditionBool {
|
||||
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
}
|
||||
|
||||
// Test last price >= 10
|
||||
Events[one].Condition = ">=,10"
|
||||
conditionBool = Events[one].CheckCondition()
|
||||
if !conditionBool {
|
||||
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
}
|
||||
|
||||
// Test last price <= 10
|
||||
Events[one].Condition = "<,100"
|
||||
conditionBool = Events[one].CheckCondition()
|
||||
if !conditionBool {
|
||||
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
}
|
||||
|
||||
// Test last price <= 10
|
||||
Events[one].Condition = "<=,100"
|
||||
conditionBool = Events[one].CheckCondition()
|
||||
if !conditionBool {
|
||||
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
}
|
||||
|
||||
Events[one].Condition = "==,11"
|
||||
conditionBool = Events[one].CheckCondition()
|
||||
if !conditionBool {
|
||||
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
}
|
||||
|
||||
Events[one].Condition = "^,11"
|
||||
conditionBool = Events[one].CheckCondition()
|
||||
if conditionBool {
|
||||
t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
}
|
||||
|
||||
if !RemoveEvent(one) {
|
||||
t.Error("Test Failed. CheckCondition: Error, error removing event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidEvent(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
err := IsValidEvent("ANX", "price", ">,==", actionTest)
|
||||
if err != nil {
|
||||
t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
}
|
||||
err = IsValidEvent("ANX", "price", ">,", actionTest)
|
||||
if err == nil {
|
||||
t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
}
|
||||
err = IsValidEvent("ANX", "Testy", ">,==", actionTest)
|
||||
if err == nil {
|
||||
t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
}
|
||||
err = IsValidEvent("Testys", "price", ">,==", actionTest)
|
||||
if err == nil {
|
||||
t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
}
|
||||
|
||||
action := "blah,blah"
|
||||
err = IsValidEvent("ANX", "price", ">=,10", action)
|
||||
if err == nil {
|
||||
t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
}
|
||||
|
||||
action = "SMS,blah"
|
||||
err = IsValidEvent("ANX", "price", ">=,10", action)
|
||||
if err == nil {
|
||||
t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
}
|
||||
|
||||
//Function tests need to appended to this function when more actions are
|
||||
//implemented
|
||||
}
|
||||
|
||||
func TestCheckEvents(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
_, err := AddEvent("ANX", "price", ">=,10", pair, "SPOT", actionTest)
|
||||
if err != nil {
|
||||
t.Fatal("Test failed. TestChcheckEvents add event")
|
||||
}
|
||||
|
||||
go CheckEvents()
|
||||
}
|
||||
|
||||
func TestIsValidExchange(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
boolean := IsValidExchange("ANX")
|
||||
if !boolean {
|
||||
t.Error("Test Failed. IsValidExchange: Error, incorrect Exchange")
|
||||
}
|
||||
boolean = IsValidExchange("OBTUSE")
|
||||
if boolean {
|
||||
t.Error("Test Failed. IsValidExchange: Error, incorrect return")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidCondition(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
boolean := IsValidCondition(">")
|
||||
if !boolean {
|
||||
t.Error("Test Failed. IsValidCondition: Error, incorrect Condition")
|
||||
}
|
||||
boolean = IsValidCondition(">=")
|
||||
if !boolean {
|
||||
t.Error("Test Failed. IsValidCondition: Error, incorrect Condition")
|
||||
}
|
||||
boolean = IsValidCondition("<")
|
||||
if !boolean {
|
||||
t.Error("Test Failed. IsValidCondition: Error, incorrect Condition")
|
||||
}
|
||||
boolean = IsValidCondition("<=")
|
||||
if !boolean {
|
||||
t.Error("Test Failed. IsValidCondition: Error, incorrect Condition")
|
||||
}
|
||||
boolean = IsValidCondition("==")
|
||||
if !boolean {
|
||||
t.Error("Test Failed. IsValidCondition: Error, incorrect Condition")
|
||||
}
|
||||
boolean = IsValidCondition("**********")
|
||||
if boolean {
|
||||
t.Error("Test Failed. IsValidCondition: Error, incorrect return")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidAction(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
boolean := IsValidAction("sms")
|
||||
if !boolean {
|
||||
t.Error("Test Failed. IsValidAction: Error, incorrect Action")
|
||||
}
|
||||
boolean = IsValidAction(actionTest)
|
||||
if !boolean {
|
||||
t.Error("Test Failed. IsValidAction: Error, incorrect Action")
|
||||
}
|
||||
boolean = IsValidAction("randomstring")
|
||||
if boolean {
|
||||
t.Error("Test Failed. IsValidAction: Error, incorrect return")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsValidItem(t *testing.T) {
|
||||
testSetup(t)
|
||||
|
||||
boolean := IsValidItem("price")
|
||||
if !boolean {
|
||||
t.Error("Test Failed. IsValidItem: Error, incorrect Item")
|
||||
}
|
||||
boolean = IsValidItem("obtuse")
|
||||
if boolean {
|
||||
t.Error("Test Failed. IsValidItem: Error, incorrect return")
|
||||
}
|
||||
}
|
||||
//
|
||||
// import (
|
||||
// "testing"
|
||||
//
|
||||
// "github.com/thrasher-/gocryptotrader/config"
|
||||
// "github.com/thrasher-/gocryptotrader/currency/pair"
|
||||
// "github.com/thrasher-/gocryptotrader/exchanges/ticker"
|
||||
// "github.com/thrasher-/gocryptotrader/smsglobal"
|
||||
// )
|
||||
//
|
||||
// var (
|
||||
// loaded = false
|
||||
// )
|
||||
//
|
||||
// func testSetup(t *testing.T) {
|
||||
// if !loaded {
|
||||
// cfg := config.GetConfig()
|
||||
// err := cfg.LoadConfig("")
|
||||
// if err != nil {
|
||||
// t.Fatalf("Test failed. Failed to load config %s", err)
|
||||
// }
|
||||
// smsglobal.New(cfg.SMS.Username, cfg.SMS.Password, cfg.Name, cfg.SMS.Contacts)
|
||||
// loaded = true
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func TestAddEvent(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
// eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
// if err != nil && eventID != 0 {
|
||||
// t.Errorf("Test Failed. AddEvent: Error, %s", err)
|
||||
// }
|
||||
// eventID, err = AddEvent("ANXX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
// if err == nil && eventID == 0 {
|
||||
// t.Error("Test Failed. AddEvent: Error, error not captured in Exchange")
|
||||
// }
|
||||
// eventID, err = AddEvent("ANX", "prices", ">,==", pair, "SPOT", actionTest)
|
||||
// if err == nil && eventID == 0 {
|
||||
// t.Error("Test Failed. AddEvent: Error, error not captured in Item")
|
||||
// }
|
||||
// eventID, err = AddEvent("ANX", "price", "3===D", pair, "SPOT", actionTest)
|
||||
// if err == nil && eventID == 0 {
|
||||
// t.Error("Test Failed. AddEvent: Error, error not captured in Condition")
|
||||
// }
|
||||
// eventID, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", "console_prints")
|
||||
// if err == nil && eventID == 0 {
|
||||
// t.Error("Test Failed. AddEvent: Error, error not captured in Action")
|
||||
// }
|
||||
//
|
||||
// if !RemoveEvent(eventID) {
|
||||
// t.Error("Test Failed. RemoveEvent: Error, error removing event")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func TestRemoveEvent(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
// eventID, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
// if err != nil && eventID != 0 {
|
||||
// t.Errorf("Test Failed. RemoveEvent: Error, %s", err)
|
||||
// }
|
||||
// if !RemoveEvent(eventID) {
|
||||
// t.Error("Test Failed. RemoveEvent: Error, error removing event")
|
||||
// }
|
||||
// if RemoveEvent(1234) {
|
||||
// t.Error("Test Failed. RemoveEvent: Error, error removing event")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func TestGetEventCounter(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
// one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
// if err != nil {
|
||||
// t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
|
||||
// }
|
||||
// two, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
// if err != nil {
|
||||
// t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
|
||||
// }
|
||||
// three, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
// if err != nil {
|
||||
// t.Errorf("Test Failed. GetEventCounter: Error, %s", err)
|
||||
// }
|
||||
//
|
||||
// Events[three-1].Executed = true
|
||||
//
|
||||
// total, _ := GetEventCounter()
|
||||
// if total <= 0 {
|
||||
// t.Errorf("Test Failed. GetEventCounter: Total = %d", total)
|
||||
// }
|
||||
// if !RemoveEvent(one) {
|
||||
// t.Error("Test Failed. GetEventCounter: Error, error removing event")
|
||||
// }
|
||||
// if !RemoveEvent(two) {
|
||||
// t.Error("Test Failed. GetEventCounter: Error, error removing event")
|
||||
// }
|
||||
// if !RemoveEvent(three) {
|
||||
// t.Error("Test Failed. GetEventCounter: Error, error removing event")
|
||||
// }
|
||||
//
|
||||
// total2, _ := GetEventCounter()
|
||||
// if total2 != 0 {
|
||||
// t.Errorf("Test Failed. GetEventCounter: Total = %d", total2)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func TestExecuteAction(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
// one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
|
||||
// }
|
||||
// isExecuted := Events[one].ExecuteAction()
|
||||
// if !isExecuted {
|
||||
// t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
// }
|
||||
// if !RemoveEvent(one) {
|
||||
// t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
// }
|
||||
//
|
||||
// action := actionSMSNotify + "," + "ALL"
|
||||
// one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
|
||||
// }
|
||||
//
|
||||
// isExecuted = Events[one].ExecuteAction()
|
||||
// if !isExecuted {
|
||||
// t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
// }
|
||||
// if !RemoveEvent(one) {
|
||||
// t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
// }
|
||||
//
|
||||
// action = actionSMSNotify + "," + "StyleGherkin"
|
||||
// one, err = AddEvent("ANX", "price", ">,==", pair, "SPOT", action)
|
||||
// if err != nil {
|
||||
// t.Fatalf("Test Failed. ExecuteAction: Error, %s", err)
|
||||
// }
|
||||
//
|
||||
// isExecuted = Events[one].ExecuteAction()
|
||||
// if !isExecuted {
|
||||
// t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
// }
|
||||
// if !RemoveEvent(one) {
|
||||
// t.Error("Test Failed. ExecuteAction: Error, error removing event")
|
||||
// }
|
||||
// // More tests when ExecuteAction is expanded
|
||||
// }
|
||||
//
|
||||
// func TestEventToString(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
// one, err := AddEvent("ANX", "price", ">,==", pair, "SPOT", actionTest)
|
||||
// if err != nil {
|
||||
// t.Errorf("Test Failed. EventToString: Error, %s", err)
|
||||
// }
|
||||
//
|
||||
// eventString := Events[one].String()
|
||||
// if eventString != "If the BTCUSD [SPOT] price on ANX is > == then ACTION_TEST." {
|
||||
// t.Error("Test Failed. EventToString: Error, incorrect return string")
|
||||
// }
|
||||
//
|
||||
// if !RemoveEvent(one) {
|
||||
// t.Error("Test Failed. EventToString: Error, error removing event")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func TestCheckCondition(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// // Test invalid currency pair
|
||||
// newPair := pair.NewCurrencyPair("A", "B")
|
||||
// one, err := AddEvent("ANX", "price", ">=,10", newPair, "SPOT", actionTest)
|
||||
// if err != nil {
|
||||
// t.Errorf("Test Failed. CheckCondition: Error, %s", err)
|
||||
// }
|
||||
// conditionBool := Events[one].CheckCondition()
|
||||
// if conditionBool {
|
||||
// t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
// }
|
||||
//
|
||||
// // Test last price == 0
|
||||
// var tickerNew ticker.Price
|
||||
// tickerNew.Last = 0
|
||||
// newPair = pair.NewCurrencyPair("BTC", "USD")
|
||||
// ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot)
|
||||
// Events[one].Pair = newPair
|
||||
// conditionBool = Events[one].CheckCondition()
|
||||
// if conditionBool {
|
||||
// t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
// }
|
||||
//
|
||||
// // Test last pricce > 0 and conditional logic
|
||||
// tickerNew.Last = 11
|
||||
// ticker.ProcessTicker("ANX", newPair, tickerNew, ticker.Spot)
|
||||
// Events[one].Condition = ">,10"
|
||||
// conditionBool = Events[one].CheckCondition()
|
||||
// if !conditionBool {
|
||||
// t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
// }
|
||||
//
|
||||
// // Test last price >= 10
|
||||
// Events[one].Condition = ">=,10"
|
||||
// conditionBool = Events[one].CheckCondition()
|
||||
// if !conditionBool {
|
||||
// t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
// }
|
||||
//
|
||||
// // Test last price <= 10
|
||||
// Events[one].Condition = "<,100"
|
||||
// conditionBool = Events[one].CheckCondition()
|
||||
// if !conditionBool {
|
||||
// t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
// }
|
||||
//
|
||||
// // Test last price <= 10
|
||||
// Events[one].Condition = "<=,100"
|
||||
// conditionBool = Events[one].CheckCondition()
|
||||
// if !conditionBool {
|
||||
// t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
// }
|
||||
//
|
||||
// Events[one].Condition = "==,11"
|
||||
// conditionBool = Events[one].CheckCondition()
|
||||
// if !conditionBool {
|
||||
// t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
// }
|
||||
//
|
||||
// Events[one].Condition = "^,11"
|
||||
// conditionBool = Events[one].CheckCondition()
|
||||
// if conditionBool {
|
||||
// t.Error("Test Failed. CheckCondition: Error, wrong conditional.")
|
||||
// }
|
||||
//
|
||||
// if !RemoveEvent(one) {
|
||||
// t.Error("Test Failed. CheckCondition: Error, error removing event")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func TestIsValidEvent(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// err := IsValidEvent("ANX", "price", ">,==", actionTest)
|
||||
// if err != nil {
|
||||
// t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
// }
|
||||
// err = IsValidEvent("ANX", "price", ">,", actionTest)
|
||||
// if err == nil {
|
||||
// t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
// }
|
||||
// err = IsValidEvent("ANX", "Testy", ">,==", actionTest)
|
||||
// if err == nil {
|
||||
// t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
// }
|
||||
// err = IsValidEvent("Testys", "price", ">,==", actionTest)
|
||||
// if err == nil {
|
||||
// t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
// }
|
||||
//
|
||||
// action := "blah,blah"
|
||||
// err = IsValidEvent("ANX", "price", ">=,10", action)
|
||||
// if err == nil {
|
||||
// t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
// }
|
||||
//
|
||||
// action = "SMS,blah"
|
||||
// err = IsValidEvent("ANX", "price", ">=,10", action)
|
||||
// if err == nil {
|
||||
// t.Errorf("Test Failed. IsValidEvent: %s", err)
|
||||
// }
|
||||
//
|
||||
// //Function tests need to appended to this function when more actions are
|
||||
// //implemented
|
||||
// }
|
||||
//
|
||||
// func TestCheckEvents(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// pair := pair.NewCurrencyPair("BTC", "USD")
|
||||
// _, err := AddEvent("ANX", "price", ">=,10", pair, "SPOT", actionTest)
|
||||
// if err != nil {
|
||||
// t.Fatal("Test failed. TestChcheckEvents add event")
|
||||
// }
|
||||
//
|
||||
// go CheckEvents()
|
||||
// }
|
||||
//
|
||||
// func TestIsValidExchange(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// boolean := IsValidExchange("ANX")
|
||||
// if !boolean {
|
||||
// t.Error("Test Failed. IsValidExchange: Error, incorrect Exchange")
|
||||
// }
|
||||
// boolean = IsValidExchange("OBTUSE")
|
||||
// if boolean {
|
||||
// t.Error("Test Failed. IsValidExchange: Error, incorrect return")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func TestIsValidCondition(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// boolean := IsValidCondition(">")
|
||||
// if !boolean {
|
||||
// t.Error("Test Failed. IsValidCondition: Error, incorrect Condition")
|
||||
// }
|
||||
// boolean = IsValidCondition(">=")
|
||||
// if !boolean {
|
||||
// t.Error("Test Failed. IsValidCondition: Error, incorrect Condition")
|
||||
// }
|
||||
// boolean = IsValidCondition("<")
|
||||
// if !boolean {
|
||||
// t.Error("Test Failed. IsValidCondition: Error, incorrect Condition")
|
||||
// }
|
||||
// boolean = IsValidCondition("<=")
|
||||
// if !boolean {
|
||||
// t.Error("Test Failed. IsValidCondition: Error, incorrect Condition")
|
||||
// }
|
||||
// boolean = IsValidCondition("==")
|
||||
// if !boolean {
|
||||
// t.Error("Test Failed. IsValidCondition: Error, incorrect Condition")
|
||||
// }
|
||||
// boolean = IsValidCondition("**********")
|
||||
// if boolean {
|
||||
// t.Error("Test Failed. IsValidCondition: Error, incorrect return")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func TestIsValidAction(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// boolean := IsValidAction("sms")
|
||||
// if !boolean {
|
||||
// t.Error("Test Failed. IsValidAction: Error, incorrect Action")
|
||||
// }
|
||||
// boolean = IsValidAction(actionTest)
|
||||
// if !boolean {
|
||||
// t.Error("Test Failed. IsValidAction: Error, incorrect Action")
|
||||
// }
|
||||
// boolean = IsValidAction("randomstring")
|
||||
// if boolean {
|
||||
// t.Error("Test Failed. IsValidAction: Error, incorrect return")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func TestIsValidItem(t *testing.T) {
|
||||
// testSetup(t)
|
||||
//
|
||||
// boolean := IsValidItem("price")
|
||||
// if !boolean {
|
||||
// t.Error("Test Failed. IsValidItem: Error, incorrect Item")
|
||||
// }
|
||||
// boolean = IsValidItem("obtuse")
|
||||
// if boolean {
|
||||
// t.Error("Test Failed. IsValidItem: Error, incorrect return")
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -7,10 +7,11 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/communications"
|
||||
"github.com/thrasher-/gocryptotrader/communications/base"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
"github.com/thrasher-/gocryptotrader/currency/pair"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-/gocryptotrader/smsglobal"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -30,6 +31,9 @@ var (
|
||||
errInvalidCondition = errors.New("invalid conditional option")
|
||||
errInvalidAction = errors.New("invalid action")
|
||||
errExchangeDisabled = errors.New("desired exchange is disabled")
|
||||
|
||||
// NOTE comms is an interim implementation
|
||||
comms *communications.Communications
|
||||
)
|
||||
|
||||
// Event struct holds the event variables
|
||||
@@ -48,6 +52,12 @@ type Event struct {
|
||||
// appended
|
||||
var Events []*Event
|
||||
|
||||
// SetComms is an interim function that will support a median integration. This
|
||||
// sets the current comms package.
|
||||
func SetComms(commsP *communications.Communications) {
|
||||
comms = commsP
|
||||
}
|
||||
|
||||
// AddEvent adds an event to the Events chain and returns an index/eventID
|
||||
// and an error
|
||||
func AddEvent(Exchange, Item, Condition string, CurrencyPair pair.CurrencyPair, Asset, Action string) (int, error) {
|
||||
@@ -106,12 +116,8 @@ func (e *Event) ExecuteAction() bool {
|
||||
action := common.SplitStrings(e.Action, ",")
|
||||
if action[0] == actionSMSNotify {
|
||||
message := fmt.Sprintf("Event triggered: %s", e.String())
|
||||
s := smsglobal.SMSGlobal
|
||||
if action[1] == "ALL" {
|
||||
s.SendMessageToAll(message)
|
||||
} else {
|
||||
contact, _ := s.GetContactByName(action[1])
|
||||
s.SendMessage(contact.Number, message)
|
||||
comms.PushEvent(base.Event{TradeDetails: message})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -213,11 +219,7 @@ func IsValidEvent(Exchange, Item, Condition, Action string) error {
|
||||
}
|
||||
|
||||
if action[1] != "ALL" {
|
||||
s := smsglobal.SMSGlobal
|
||||
_, err := s.GetContactByName(action[1])
|
||||
if err != nil {
|
||||
return errInvalidAction
|
||||
}
|
||||
comms.PushEvent(base.Event{Type: action[1]})
|
||||
}
|
||||
} else {
|
||||
if Action != actionConsolePrint && Action != actionTest {
|
||||
|
||||
27
main.go
27
main.go
@@ -15,19 +15,19 @@ import (
|
||||
"github.com/thrasher-/gocryptotrader/currency/forexprovider"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
"github.com/thrasher-/gocryptotrader/communications"
|
||||
"github.com/thrasher-/gocryptotrader/config"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges"
|
||||
"github.com/thrasher-/gocryptotrader/portfolio"
|
||||
"github.com/thrasher-/gocryptotrader/smsglobal"
|
||||
)
|
||||
|
||||
// Bot contains configuration, portfolio, exchange & ticker data and is the
|
||||
// overarching type across this code base.
|
||||
type Bot struct {
|
||||
config *config.Config
|
||||
smsglobal *smsglobal.Base
|
||||
portfolio *portfolio.Base
|
||||
exchanges []exchange.IBotExchange
|
||||
comms *communications.Communications
|
||||
shutdown chan bool
|
||||
dryRun bool
|
||||
configFile string
|
||||
@@ -82,21 +82,9 @@ func main() {
|
||||
log.Printf("Bot '%s' started.\n", bot.config.Name)
|
||||
log.Printf("Bot dry run mode: %v.\n", common.IsEnabled(bot.dryRun))
|
||||
|
||||
if bot.config.SMS.Enabled {
|
||||
bot.smsglobal = smsglobal.New(bot.config.SMS.Username, bot.config.SMS.Password,
|
||||
bot.config.Name, bot.config.SMS.Contacts)
|
||||
log.Printf(
|
||||
"SMS support enabled. Number of SMS contacts %d.\n",
|
||||
bot.smsglobal.GetEnabledContacts(),
|
||||
)
|
||||
} else {
|
||||
log.Println("SMS support disabled.")
|
||||
}
|
||||
|
||||
log.Printf(
|
||||
"Available Exchanges: %d. Enabled Exchanges: %d.\n",
|
||||
len(bot.config.Exchanges), bot.config.CountEnabledExchanges(),
|
||||
)
|
||||
log.Printf("Available Exchanges: %d. Enabled Exchanges: %d.\n",
|
||||
len(bot.config.Exchanges),
|
||||
bot.config.CountEnabledExchanges())
|
||||
|
||||
common.HTTPClient = common.NewHTTPClientWithTimeout(bot.config.GlobalHTTPTimeout)
|
||||
log.Printf("Global HTTP request timeout: %v.\n", common.HTTPClient.Timeout)
|
||||
@@ -106,6 +94,10 @@ func main() {
|
||||
log.Fatalf("No exchanges were able to be loaded. Exiting")
|
||||
}
|
||||
|
||||
log.Println("Starting communication mediums..")
|
||||
bot.comms = communications.NewComm(bot.config.GetCommunicationsConfig())
|
||||
bot.comms.GetEnabledCommunicationMediums()
|
||||
|
||||
log.Printf("Fiat display currency: %s.", bot.config.Currency.FiatDisplayCurrency)
|
||||
currency.BaseCurrency = bot.config.Currency.FiatDisplayCurrency
|
||||
currency.FXProviders = forexprovider.StartFXService(bot.config.GetCurrencyConfig().ForexProviders)
|
||||
@@ -124,6 +116,7 @@ func main() {
|
||||
bot.portfolio = &portfolio.Portfolio
|
||||
bot.portfolio.SeedPortfolio(bot.config.Portfolio)
|
||||
SeedExchangeAccountInfo(GetAllEnabledExchangeAccountInfo().Data)
|
||||
|
||||
go portfolio.StartPortfolioWatcher()
|
||||
go TickerUpdaterRoutine()
|
||||
go OrderbookUpdaterRoutine()
|
||||
|
||||
14
routines.go
14
routines.go
@@ -206,8 +206,11 @@ func TickerUpdaterRoutine() {
|
||||
result, err = exch.GetTickerPrice(c, assetType)
|
||||
}
|
||||
printTickerSummary(result, c, assetType, exchangeName, err)
|
||||
if bot.config.Webserver.Enabled && err == nil {
|
||||
relayWebsocketEvent(result, "ticker_update", assetType, exchangeName)
|
||||
if err == nil {
|
||||
bot.comms.StageTickerData(exchangeName, assetType, result)
|
||||
if bot.config.Webserver.Enabled {
|
||||
relayWebsocketEvent(result, "ticker_update", assetType, exchangeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,8 +257,11 @@ func OrderbookUpdaterRoutine() {
|
||||
processOrderbook := func(exch exchange.IBotExchange, c pair.CurrencyPair, assetType string) {
|
||||
result, err := exch.UpdateOrderbook(c, assetType)
|
||||
printOrderbookSummary(result, c, assetType, exchangeName, err)
|
||||
if bot.config.Webserver.Enabled && err == nil {
|
||||
relayWebsocketEvent(result, "orderbook_update", assetType, exchangeName)
|
||||
if err == nil {
|
||||
bot.comms.StageOrderbookData(exchangeName, assetType, result)
|
||||
if bot.config.Webserver.Enabled {
|
||||
relayWebsocketEvent(result, "orderbook_update", assetType, exchangeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# GoCryptoTrader package Smsglobal
|
||||
|
||||
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/page-logo.png?raw=true" width="350px" height="350px" hspace="70">
|
||||
|
||||
|
||||
[](https://travis-ci.org/thrasher-/gocryptotrader)
|
||||
[](https://github.com/thrasher-/gocryptotrader/blob/master/LICENSE)
|
||||
[](https://godoc.org/github.com/thrasher-/gocryptotrader/smsglobal)
|
||||
[](http://codecov.io/github/thrasher-/gocryptotrader?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/thrasher-/gocryptotrader)
|
||||
|
||||
|
||||
This smsglobal package is part of the GoCryptoTrader codebase.
|
||||
|
||||
## This is still in active development
|
||||
|
||||
You can track ideas, planned features and what's in progresss on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader).
|
||||
|
||||
Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://gocryptotrader.herokuapp.com/)
|
||||
|
||||
## Current Features for smsglobal
|
||||
|
||||
+ This package allows for the messaging of events to a personal phone number or a group of phone numbers.
|
||||
|
||||
### Please click GoDocs chevron above to view current GoDoc information for this package
|
||||
|
||||
## Contribution
|
||||
|
||||
Please feel free to submit any pull requests or suggest any desired features to be added.
|
||||
|
||||
When submitting a PR, please abide by our coding guidelines:
|
||||
|
||||
+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||
+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||
+ Code must adhere to our [coding style](https://github.com/thrasher-/gocryptotrader/blob/master/doc/coding_style.md).
|
||||
+ Pull requests need to be based on and opened against the `master` branch.
|
||||
|
||||
## Donations
|
||||
|
||||
<img src="https://github.com/thrasher-/gocryptotrader/blob/master/web/src/assets/donate.png?raw=true" hspace="70">
|
||||
|
||||
If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to:
|
||||
|
||||
***1F5zVDgNjorJ51oGebSvNCrSAHpwGkUdDB***
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
package smsglobal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/common"
|
||||
)
|
||||
|
||||
const (
|
||||
smsGlobalAPIURL = "https://www.smsglobal.com/http-api.php"
|
||||
// ErrSMSContactNotFound is a general error code for "SMS Contact not found."
|
||||
ErrSMSContactNotFound = "SMS Contact not found."
|
||||
errSMSNotSent = "SMS message not sent."
|
||||
)
|
||||
|
||||
// vars for the SMS global package
|
||||
var (
|
||||
SMSGlobal *Base
|
||||
)
|
||||
|
||||
// Contact struct stores information related to a SMSGlobal contact
|
||||
type Contact struct {
|
||||
Name string `json:"name"`
|
||||
Number string `json:"number"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
// Base struct stores information related to the SMSGlobal package
|
||||
type Base struct {
|
||||
Contacts []Contact `json:"contacts"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
SendFrom string `json:"send_from"`
|
||||
}
|
||||
|
||||
// New initialises the SMSGlobal var
|
||||
func New(username, password, sendFrom string, contacts []Contact) *Base {
|
||||
if username == "" || password == "" || sendFrom == "" || len(contacts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var goodContacts []Contact
|
||||
for x := range contacts {
|
||||
if contacts[x].Name != "" || contacts[x].Number != "" {
|
||||
goodContacts = append(goodContacts, contacts[x])
|
||||
}
|
||||
}
|
||||
|
||||
SMSGlobal = &Base{
|
||||
Contacts: goodContacts,
|
||||
Username: username,
|
||||
Password: password,
|
||||
SendFrom: sendFrom,
|
||||
}
|
||||
return SMSGlobal
|
||||
}
|
||||
|
||||
// GetEnabledContacts returns how many SMS contacts are enabled in the
|
||||
// contact list
|
||||
func (s *Base) GetEnabledContacts() int {
|
||||
counter := 0
|
||||
for x := range s.Contacts {
|
||||
if s.Contacts[x].Enabled {
|
||||
counter++
|
||||
}
|
||||
}
|
||||
return counter
|
||||
}
|
||||
|
||||
// GetContactByNumber returns a contact with supplied number
|
||||
func (s *Base) GetContactByNumber(number string) (Contact, error) {
|
||||
for x := range s.Contacts {
|
||||
if s.Contacts[x].Number == number {
|
||||
return s.Contacts[x], nil
|
||||
}
|
||||
}
|
||||
return Contact{}, errors.New(ErrSMSContactNotFound)
|
||||
}
|
||||
|
||||
// GetContactByName returns a contact with supplied name
|
||||
func (s *Base) GetContactByName(name string) (Contact, error) {
|
||||
for x := range s.Contacts {
|
||||
if common.StringToLower(s.Contacts[x].Name) == common.StringToLower(name) {
|
||||
return s.Contacts[x], nil
|
||||
}
|
||||
}
|
||||
return Contact{}, errors.New(ErrSMSContactNotFound)
|
||||
}
|
||||
|
||||
// AddContact checks to see if a contact exists and adds them if it doesn't
|
||||
func (s *Base) AddContact(contact Contact) {
|
||||
if contact.Name == "" || contact.Number == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if s.ContactExists(contact) {
|
||||
return
|
||||
}
|
||||
|
||||
s.Contacts = append(s.Contacts, contact)
|
||||
}
|
||||
|
||||
// ContactExists checks to see if a contact exists
|
||||
func (s *Base) ContactExists(contact Contact) bool {
|
||||
for x := range s.Contacts {
|
||||
if s.Contacts[x].Number == contact.Number && common.StringToLower(s.Contacts[x].Name) == common.StringToLower(contact.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveContact removes a contact if it exists
|
||||
func (s *Base) RemoveContact(contact Contact) {
|
||||
if !s.ContactExists(contact) {
|
||||
return
|
||||
}
|
||||
|
||||
for x := range s.Contacts {
|
||||
if s.Contacts[x].Name == contact.Name && s.Contacts[x].Number == contact.Number {
|
||||
s.Contacts = append(s.Contacts[:x], s.Contacts[x+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendMessageToAll sends a message to all enabled contacts in cfg
|
||||
func (s *Base) SendMessageToAll(message string) {
|
||||
for x := range s.Contacts {
|
||||
if s.Contacts[x].Enabled {
|
||||
s.SendMessage(s.Contacts[x].Name, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendMessage sends a message to an individual contact
|
||||
func (s *Base) SendMessage(to, message string) error {
|
||||
if flag.Lookup("test.v") != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := url.Values{}
|
||||
values.Set("action", "sendsms")
|
||||
values.Set("user", s.Username)
|
||||
values.Set("password", s.Password)
|
||||
values.Set("from", s.SendFrom)
|
||||
values.Set("to", to)
|
||||
values.Set("text", message)
|
||||
|
||||
headers := make(map[string]string)
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
||||
|
||||
resp, err := common.SendHTTPRequest(
|
||||
"POST", smsGlobalAPIURL, headers, strings.NewReader(values.Encode()),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !common.StringContains(resp, "OK: 0; Sent queued message") {
|
||||
return errors.New(errSMSNotSent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package smsglobal
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
result := New("", "", "", nil)
|
||||
if result != nil {
|
||||
t.Error("Test failed. New: Expected nil result")
|
||||
}
|
||||
|
||||
contact := Contact{Name: "bob", Number: "1234", Enabled: true}
|
||||
var contacts []Contact
|
||||
contacts = append(contacts, contact)
|
||||
|
||||
result = New("bob", "pw", "Skynet", contacts)
|
||||
if !result.ContactExists(contact) {
|
||||
t.Error("Test failed. New: Expected contact not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEnabledContacts(t *testing.T) {
|
||||
contact := Contact{Name: "bob", Number: "1234", Enabled: true}
|
||||
var contacts []Contact
|
||||
contacts = append(contacts, contact)
|
||||
result := New("bob", "pw", "Skynet", contacts)
|
||||
|
||||
expected := 1
|
||||
actual := result.GetEnabledContacts()
|
||||
if expected != actual {
|
||||
t.Errorf("Test failed. TestGetEnabledContacts expected %d, got %d",
|
||||
expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContactByNumber(t *testing.T) {
|
||||
contact := Contact{Name: "bob", Number: "1234", Enabled: true}
|
||||
var contacts []Contact
|
||||
contacts = append(contacts, contact)
|
||||
result := New("bob", "pw", "Skynet", contacts)
|
||||
|
||||
actual, err := result.GetContactByNumber(contact.Number)
|
||||
if err != nil {
|
||||
t.Fatalf("Test failed. TestGetContactByNumber: %s", err)
|
||||
}
|
||||
|
||||
if actual.Name != contact.Name && actual.Number != contact.Number && actual.Enabled != contact.Enabled {
|
||||
t.Fatal("Test failed. TestGetContactByNumber: Incorrect values")
|
||||
}
|
||||
|
||||
_, err = result.GetContactByNumber("ASDASDASD")
|
||||
if err == nil {
|
||||
t.Fatal("Test failed. TestGetContactByNumber: Returned nil err on non-existent number")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetContactByName(t *testing.T) {
|
||||
contact := Contact{Name: "bob", Number: "1234", Enabled: true}
|
||||
var contacts []Contact
|
||||
contacts = append(contacts, contact)
|
||||
result := New("bob", "pw", "Skynet", contacts)
|
||||
|
||||
actual, err := result.GetContactByName(contact.Name)
|
||||
if err != nil {
|
||||
t.Fatalf("Test failed. TestGetContactByName: %s", err)
|
||||
}
|
||||
|
||||
if actual.Name != contact.Name && actual.Number != contact.Number && actual.Enabled != contact.Enabled {
|
||||
t.Fatal("Test failed. TestGetContactByName: Incorrect values")
|
||||
}
|
||||
|
||||
_, err = result.GetContactByName("ASDASDASD")
|
||||
if err == nil {
|
||||
t.Fatal("Test failed. TestGetContactByName: Returned nil err on non-existent number")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddContact(t *testing.T) {
|
||||
contact := Contact{Name: "bob", Number: "1234", Enabled: true}
|
||||
var contacts []Contact
|
||||
contacts = append(contacts, contact)
|
||||
result := New("bob", "pw", "Skynet", contacts)
|
||||
|
||||
// Test adding same contact
|
||||
result.AddContact(contact)
|
||||
if result.GetEnabledContacts() > 1 {
|
||||
t.Fatal("Test failed. TestAddContact: Incorrect values")
|
||||
}
|
||||
|
||||
invalidContact := Contact{Name: "", Number: "", Enabled: true}
|
||||
result.AddContact(invalidContact)
|
||||
if result.GetEnabledContacts() > 1 {
|
||||
t.Fatal("Test failed. TestAddContact: Incorrect values")
|
||||
}
|
||||
|
||||
newContact := Contact{Name: "newContact", Number: "12345", Enabled: true}
|
||||
result.AddContact(newContact)
|
||||
if result.GetEnabledContacts() != 2 {
|
||||
t.Fatal("Test failed. TestAddContact: Incorrect values")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContact(t *testing.T) {
|
||||
contact := Contact{Name: "bob", Number: "1234", Enabled: true}
|
||||
var contacts []Contact
|
||||
contacts = append(contacts, contact)
|
||||
result := New("bob", "pw", "Skynet", contacts)
|
||||
|
||||
result.RemoveContact(Contact{Name: "blah", Number: "1234"})
|
||||
if result.GetEnabledContacts() != 1 {
|
||||
t.Fatal("Test failed. TestRemoveContact: Incorrect values")
|
||||
}
|
||||
|
||||
result.RemoveContact(contact)
|
||||
if result.GetEnabledContacts() != 0 {
|
||||
t.Fatal("Test failed. TestRemoveContact: Incorrect values")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendMessageToAll(t *testing.T) {
|
||||
contact := Contact{Name: "bob", Number: "1234", Enabled: true}
|
||||
var contacts []Contact
|
||||
contacts = append(contacts, contact)
|
||||
result := New("bob", "pw", "Skynet", contacts)
|
||||
result.SendMessageToAll("hello world")
|
||||
}
|
||||
|
||||
func TestSendMessage(t *testing.T) {
|
||||
contact := Contact{Name: "bob", Number: "1234", Enabled: true}
|
||||
var contacts []Contact
|
||||
contacts = append(contacts, contact)
|
||||
result := New("bob", "pw", "Skynet", contacts)
|
||||
err := result.SendMessage(contact.Number, "hello world")
|
||||
log.Println(err)
|
||||
t.Log(err)
|
||||
}
|
||||
53
testdata/configtest.json
vendored
53
testdata/configtest.json
vendored
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"Name": "Skynet",
|
||||
"EncryptConfig": 0,
|
||||
"GlobalHTTPTimeout": 15000000000,
|
||||
"CurrencyConfig": {
|
||||
"ForexProviders": [
|
||||
{
|
||||
@@ -47,7 +48,45 @@
|
||||
},
|
||||
"FiatDisplayCurrency": "USD"
|
||||
},
|
||||
"GlobalHTTPTimeout": 15000000000,
|
||||
"Communications": {
|
||||
"Slack": {
|
||||
"Name": "Slack",
|
||||
"Enabled": false,
|
||||
"Verbose": false,
|
||||
"TargetChannel": "general",
|
||||
"VerificationToken": "testtest"
|
||||
},
|
||||
"SMSGlobal": {
|
||||
"Name": "SMSGlobal",
|
||||
"Enabled": true,
|
||||
"Verbose": false,
|
||||
"Username": "1234",
|
||||
"Password": "12334",
|
||||
"Contacts": [
|
||||
{
|
||||
"Name": "StyleGherkin",
|
||||
"Number": "1231424",
|
||||
"Enabled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"SMTP": {
|
||||
"Name": "SMTP",
|
||||
"Enabled": false,
|
||||
"Verbose": false,
|
||||
"Host": "smtp.google.com",
|
||||
"Port": "537",
|
||||
"AccountName": "some",
|
||||
"AccountPassword": "password",
|
||||
"RecipientList": "lol123@gmail.com"
|
||||
},
|
||||
"Telegram": {
|
||||
"Name": "Telegram",
|
||||
"Enabled": false,
|
||||
"Verbose": false,
|
||||
"VerificationToken": "testest"
|
||||
}
|
||||
},
|
||||
"PortfolioAddresses": {
|
||||
"Addresses": [
|
||||
{
|
||||
@@ -76,18 +115,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SMSGlobal": {
|
||||
"Enabled": true,
|
||||
"Username": "1234",
|
||||
"Password": "12334",
|
||||
"Contacts": [
|
||||
{
|
||||
"name": "StyleGherkin",
|
||||
"number": "1231424",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"Webserver": {
|
||||
"Enabled": false,
|
||||
"AdminUsername": "admin",
|
||||
|
||||
Reference in New Issue
Block a user