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) != 4 && 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) > 2 { username = parts[1] password = parts[2] if len(parts) == 4 { tok = parts[3] } } 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("%02dh%02dm", h, m) } func locateVehicle(m chat1.MsgSummary) { v := getVehicle(m) if v == nil { return } ds, err := v.DriveState() if err != nil { handleError(err, m, "There was an error getting drive state. Contact @rudi9719 for more information with code %+v") return } k.SendMessageByConvID(m.ConvID, fmt.Sprintf("https://whoogle.nmare.net/search?q=%+v,%+v", ds.Latitude, ds.Longitude)) } 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) } }