package main
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/google/uuid"
"samhofi.us/x/keybase/v2/types/chat1"
)
func reset ( m chat1 . MsgSummary ) {
_ , err := k . KVDelete ( & m . Channel . Name , "teslabot" , "authtok" )
if err != nil {
handleError ( err , m , "There was an error resetting your authentication. Contact @rudi9719 for more information with code %+v" )
return
}
_ , err = k . KVDelete ( & m . Channel . Name , "teslabot" , "startPass" )
if err != nil {
handleError ( err , m , "There was an error resetting your authentication. Contact @rudi9719 for more information with code %+v" )
return
}
k . SendMessageByConvID ( m . ConvID , "Your credentials have been reset successfully." )
}
func authenticate ( m chat1 . MsgSummary ) {
defer log . PanicSafe ( )
if isAuthenticated ( m ) {
k . SendMessageByConvID ( m . ConvID , "You have already authenticated (please use !reset to reset." )
return
}
if ! m . IsEphemeral {
k . SendMessageByConvID ( m . ConvID , "Please remember to delete your message after we have authenticated!" )
}
parts := strings . Split ( m . Content . Text . Body , " " )
if len ( parts ) != 3 && len ( parts ) != 2 {
k . SendMessageByConvID ( m . ConvID , "Invalid input for command authenticate. Requires username and password. This information is not stored in keybase, or logged. %+v" , len ( parts ) )
return
}
var username , password , tok string
if len ( parts ) == 3 {
username = parts [ 1 ]
password = parts [ 2 ]
} else {
tok = parts [ 1 ]
}
t , err := login ( context . Background ( ) , username , password , tok )
if err != nil {
handleError ( err , m , "There was an error logging in. Contact @rudi9719 for more information with code %+v" )
return
}
log . LogDebug ( "Token created for %+v" , m . Sender . Username )
_ , err = k . KVPut ( & m . Channel . Name , "teslabot" , "authtok" , t )
if err != nil {
handleError ( err , m , "There was an error storing your auth token. Contact @rudi9719 for more information with code %+v" )
return
}
k . ReactByConvID ( m . ConvID , m . Id , ":car:" )
k . DeleteByConvID ( m . ConvID , m . Id )
k . SendMessageByConvID ( m . ConvID , "You're all set!" )
}
func listVehicles ( m chat1 . MsgSummary ) {
c := getTeslaClient ( m )
if c == nil {
return
}
v , err := c . Vehicles ( )
if err != nil {
handleError ( err , m , "There was an error listing vehicles. Contact @rudi9719 for more information with code %+v" )
return
}
ret := "Detected vehicles for account: ```"
for _ , v := range v {
ret += fmt . Sprintf ( "VIN: %s\n" , v . Vin )
ret += fmt . Sprintf ( "Name: %s\n\n" , v . DisplayName )
}
ret += "```"
k . SendMessageByConvID ( m . ConvID , ret )
}
func deferTime ( m chat1 . MsgSummary ) {
parts := strings . Split ( m . Content . Text . Body , " " )
t := parts [ 1 ]
start := time . Now ( )
command := strings . Join ( parts [ 2 : ] , " " )
m . Content . Text . Body = fmt . Sprintf ( "!%+v" , command )
timer , err := time . ParseDuration ( t )
if err != nil {
handleError ( err , m , "There was an error parsing your time input. Contact @rudi9719 for more information with code %+v" )
return
}
k . SendMessageByConvID ( m . ConvID , fmt . Sprintf ( "I will run `%+v` at %+v" , command , time . Now ( ) . Add ( timer ) . Format ( "Jan _2 15:04:05" ) ) )
time . Sleep ( timer )
k . SendMessageByConvID ( m . ConvID , fmt . Sprintf ( "Running `%+v` from %+v" , command , start . Format ( "Jan _2 15:04:05" ) ) )
handleChat ( m )
}
func honk ( m chat1 . MsgSummary ) {
v := getVehicle ( m )
if v == nil {
return
}
err := v . HonkHorn ( )
if err != nil {
handleError ( err , m , "There was an error honking your horn. Contact @rudi9719 for more information with code %+v" )
return
}
k . SendMessageByConvID ( m . ConvID , "I've honked your horn!" )
}
func chargeStatus ( m chat1 . MsgSummary ) {
v := getVehicle ( m )
if v == nil {
return
}
state , err := v . ChargeState ( )
if err != nil {
handleError ( err , m , "There was an error getting charge state. Contact @rudi9719 for more information with code %+v" )
return
}
ret := fmt . Sprintf ( "Status for %+v: ```" , v . DisplayName )
ret += fmt . Sprintf ( "\nCurrent Charge: %+v (%+vmi)" , state . BatteryLevel , state . BatteryRange )
if state . ChargingState != "Disconnected" {
ret += fmt . Sprintf ( "\nConnected Cable: %+v" , state . ConnChargeCable )
ret += fmt . Sprintf ( "\nCharging State: %+v" , state . ChargingState )
if state . ChargingState != "Stopped" {
chargeTimer := time . Duration ( state . MinutesToFullCharge ) * time . Minute
ret += fmt . Sprintf ( "\nTime to full: %+v (%+v)" , formatDuration ( chargeTimer ) , time . Now ( ) . Add ( chargeTimer ) . Format ( "15:04" ) )
if state . FastChargerPresent {
ret += fmt . Sprintf ( "\nFast Charger: %+v %+v" , state . FastChargerBrand , state . FastChargerType )
}
}
}
ret += "```\n"
if state . BatteryHeaterOn {
ret += "The battery heater is on. "
}
if state . ChargePortDoorOpen && state . ChargingState == "Disconnected" {
ret += "The charge port is open. "
}
k . SendMessageByConvID ( m . ConvID , ret )
}
func formatDuration ( d time . Duration ) string {
d = d . Round ( time . Minute )
h := d / time . Hour
d -= h * time . Hour
m := d / time . Minute
return fmt . Sprintf ( "%02d:%02d" , h , m )
}
func flashLights ( m chat1 . MsgSummary ) {
v := getVehicle ( m )
if v == nil {
return
}
err := v . FlashLights ( )
if err != nil {
handleError ( err , m , "There was an error flashing your lights. Contact @rudi9719 for more information with code %+v" )
return
}
k . SendMessageByConvID ( m . ConvID , "I've flashed your lights!" )
}
func currentTemp ( m chat1 . MsgSummary ) {
v := getVehicle ( m )
if v == nil {
return
}
guiSettings , err := v . GuiSettings ( )
if err != nil {
handleError ( err , m , "There was an error getting your preferences. Contact @rudi9719 for more information with code %+v" )
return
}
climateState , err := v . ClimateState ( )
if err != nil {
handleError ( err , m , "There was an error getting your Climate State. Contact @rudi9719 for more information with code %+v" )
return
}
tempSetting := climateState . DriverTempSetting
insideTemp := climateState . InsideTemp
if guiSettings . GuiTemperatureUnits == "F" {
tempSetting = ( climateState . DriverTempSetting * 1.8 ) + 32
insideTemp = ( climateState . InsideTemp * 1.8 ) + 32
}
if climateState . IsClimateOn {
k . SendMessageByConvID ( m . ConvID , "Your climate on and set to %.0f, current temp is: %.0f inside %+v" ,
tempSetting , insideTemp , v . DisplayName )
} else {
k . SendMessageByConvID ( m . ConvID , "Your climate off but set to %.0f, current temp is %.0f inside %+v" ,
tempSetting , insideTemp , v . DisplayName )
}
}
func setClimate ( m chat1 . MsgSummary ) {
v := getVehicle ( m )
if v == nil {
return
}
guiSettings , err := v . GuiSettings ( )
if err != nil {
handleError ( err , m , "There was an error getting your preferences. Contact @rudi9719 for more information with code %+v" )
return
}
parts := strings . Split ( m . Content . Text . Body , " " )
for _ , val := range parts {
if val == "on" {
err := v . StartAirConditioning ( )
if err != nil {
handleError ( err , m , "There was an error starting your Climate. Contact @rudi9719 for more information with code %+v" )
return
}
}
if val == "off" {
err := v . StopAirConditioning ( )
if err != nil {
handleError ( err , m , "There was an error turning off your Climate. Contact @rudi9719 for more information with code %+v" )
return
}
}
if temp , err := strconv . Atoi ( val ) ; err == nil {
if guiSettings . GuiTemperatureUnits == "F" {
temp = ( temp - 32 ) * 5 / 9
}
err = v . SetTemperature ( float64 ( temp ) , float64 ( temp ) )
if err != nil {
handleError ( err , m , "There was an error setting your Climate. Contact @rudi9719 for more information with code %+v" )
return
}
err = v . StartAirConditioning ( )
if err != nil {
handleError ( err , m , "There was an error starting your Climate. Contact @rudi9719 for more information with code %+v" )
return
}
}
}
climateState , err := v . ClimateState ( )
if err != nil {
handleError ( err , m , "There was an error getting your Climate State. Contact @rudi9719 for more information with code %+v" )
return
}
tempSetting := climateState . DriverTempSetting
insideTemp := climateState . InsideTemp
if guiSettings . GuiTemperatureUnits == "F" {
tempSetting = ( climateState . DriverTempSetting * 1.8 ) + 32
insideTemp = ( climateState . InsideTemp * 1.8 ) + 32
}
if climateState . IsClimateOn {
k . SendMessageByConvID ( m . ConvID , "Your climate on and set to %.0f, current temp is: %.0f" , tempSetting , insideTemp )
} else {
k . SendMessageByConvID ( m . ConvID , "Your climate off but set to %.0f" , tempSetting )
}
}
func lockVehicle ( m chat1 . MsgSummary ) {
v := getVehicle ( m )
if v == nil {
return
}
err := v . LockDoors ( )
if err != nil {
handleError ( err , m , "There was an error locking your doors. Contact @rudi9719 for more information with code %+v" )
return
}
}
func unlockVehicle ( m chat1 . MsgSummary ) {
v := getVehicle ( m )
if v == nil {
return
}
err := v . UnlockDoors ( )
if err != nil {
handleError ( err , m , "There was an error unlocking your doors. Contact @rudi9719 for more information with code %+v" )
return
}
}
func startCharge ( m chat1 . MsgSummary ) {
v := getVehicle ( m )
if v == nil {
return
}
state , err := v . ChargeState ( )
if err != nil {
handleError ( err , m , "There was an error getting charge state. Contact @rudi9719 for more information with code %+v" )
return
}
if state . ChargingState != "Disconnected" && state . FastChargerBrand != "Tesla" {
err := v . StartCharging ( )
if err != nil {
handleError ( err , m , "There was an error starting your charge. Contact @rudi9719 for more information with code %+v" )
return
}
} else {
k . SendMessageByConvID ( m . ConvID , "You must plug in to an L1/L2 charger to use this command." )
}
}
func stopCharge ( m chat1 . MsgSummary ) {
v := getVehicle ( m )
if v == nil {
return
}
state , err := v . ChargeState ( )
if err != nil {
handleError ( err , m , "There was an error getting charge state. Contact @rudi9719 for more information with code %+v" )
return
}
if state . ChargingState != "Disconnected" && state . FastChargerBrand != "Tesla" {
err := v . StopCharging ( )
if err != nil {
handleError ( err , m , "There was an error stopping your charge. Contact @rudi9719 for more information with code %+v" )
return
}
} else {
k . SendMessageByConvID ( m . ConvID , "You must plug in to an L1/L2 charger to use this command." )
}
}
func enableStart ( m chat1 . MsgSummary ) {
parts := strings . Split ( m . Content . Text . Body , " " )
if len ( parts ) != 3 {
k . SendMessageByConvID ( m . ConvID , "You must 'accept' this command and supply your password. This command is not as safe, as it stores your password in KVStore." )
return
}
if ! strings . Contains ( parts [ 1 ] , "accept" ) {
k . SendMessageByConvID ( m . ConvID , "You must 'accept' this command and supply your password. This command is not as safe, as it stores your password in KVStore." )
return
}
_ , err := k . KVPut ( & m . Channel . Name , "teslabot" , "startPass" , parts [ 2 ] )
if err != nil {
handleError ( err , m , "There was an error storing your password. Contact @rudi9719 for more information with code %+v" )
return
}
}
func disableStart ( m chat1 . MsgSummary ) {
_ , err := k . KVDelete ( & m . Channel . Name , "teslabot" , "startPass" )
if err != nil {
handleError ( err , m , "There was an error deleting your password. Contact @rudi9719 for more information with code %+v" )
return
}
}
func startVehicle ( m chat1 . MsgSummary ) {
v := getVehicle ( m )
if v == nil {
return
}
test , _ := k . KVGet ( & m . Channel . Name , "teslabot" , "startPass" )
if test . EntryValue == "" {
k . SendMessageByConvID ( m . ConvID , "You must first !enablestart to use this command." )
return
}
err := v . Start ( test . EntryValue )
if err != nil {
handleError ( err , m , "There was an error starting your vehicle. Contact @rudi9719 for more information with code %+v" )
return
}
k . SendMessageByConvID ( m . ConvID , "Your vehicle has been started!" )
}
func openTrunk ( m chat1 . MsgSummary ) {
v := getVehicle ( m )
parts := strings . Split ( m . Content . Text . Body , " " )
if len ( parts ) != 2 {
k . SendMessageByConvID ( m . ConvID , "You must supply front or rear." )
return
}
trunk := parts [ 1 ]
switch trunk {
case "rear" :
case "front" :
err := v . OpenTrunk ( strings . ToLower ( trunk ) )
if err != nil {
handleError ( err , m , "There was an error opening your trunk. Contact @rudi9719 for more information with code %+v" )
return
}
default :
k . SendMessageByConvID ( m . ConvID , "You must supply front or rear." )
return
}
}
func handleError ( err error , m chat1 . MsgSummary , msg string ) {
tracker := uuid . NewString ( )
log . LogError ( "%+v: %+v" , tracker , err )
if strings . HasPrefix ( err . Error ( ) , "405" ) {
k . SendMessageByConvID ( m . ConvID , "Tesla returned an error. Please ensure your vehicle isn't currently being serviced." )
} else if strings . HasPrefix ( err . Error ( ) , "400" ) {
k . SendMessageByConvID ( m . ConvID , "Tesla returned an error. The command you tried to use may need an update. Please contact @rudi9719 with tracking ID %+v and the bad command." , tracker )
} else if ! m . Content . Attachment . Uploaded && strings . HasPrefix ( err . Error ( ) , "408" ) {
k . SendMessageByConvID ( m . ConvID , "Unable to wake vehicle within the timeframe. Trying to run the command again." )
m . Content . Attachment . Uploaded = true
handleChat ( m )
} else {
k . SendMessageByConvID ( m . ConvID , msg , tracker )
}
}