diff --git a/command/ping.go b/bot/handler/ping.go similarity index 97% rename from command/ping.go rename to bot/handler/ping.go index 1566809..da20bac 100644 --- a/command/ping.go +++ b/bot/handler/ping.go @@ -1,4 +1,4 @@ -package command +package handler import ( "strings" diff --git a/command/random.go b/bot/handler/random.go similarity index 94% rename from command/random.go rename to bot/handler/random.go index 2f61581..5beaed0 100644 --- a/command/random.go +++ b/bot/handler/random.go @@ -1,4 +1,4 @@ -package command +package handler import ( "errors" @@ -8,6 +8,7 @@ import ( "strings" "git.kill0.net/chill9/beepboop/bot" + "git.kill0.net/chill9/beepboop/lib" "github.com/bwmarrin/discordgo" log "github.com/sirupsen/logrus" @@ -101,14 +102,14 @@ func ParseRoll(roll string) (*Roll, error) { func (r *Roll) RollDice() { for i := 1; i <= r.N; i++ { - roll := RandInt(1, r.D) + roll := lib.RandInt(1, r.D) r.Rolls = append(r.Rolls, roll) r.Sum += roll } } func (c *Coin) Flip() bool { - *c = Coin(Itob(RandInt(0, 1))) + *c = Coin(lib.Itob(lib.RandInt(0, 1))) return bool(*c) } @@ -119,7 +120,7 @@ func NewGun() *Gun { func (g *Gun) Load(n int) { g.N = 0 for i := 1; i <= n; { - x := RandInt(0, len(g.C)-1) + x := lib.RandInt(0, len(g.C)-1) if g.C[x] == false { g.C[x] = true i++ @@ -191,7 +192,7 @@ func (h *RollHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { r.RollDice() log.Debugf("rolled dice: %+v", r) - msg = fmt.Sprintf("🎲 %s = %d", JoinInt(r.Rolls, " + "), r.Sum) + msg = fmt.Sprintf("🎲 %s = %d", lib.JoinInt(r.Rolls, " + "), r.Sum) s.ChannelMessageSend(m.ChannelID, msg) } diff --git a/bot/handlers/reaction.go b/bot/handler/reaction.go similarity index 78% rename from bot/handlers/reaction.go rename to bot/handler/reaction.go index 99ec608..1e23522 100644 --- a/bot/handlers/reaction.go +++ b/bot/handler/reaction.go @@ -5,7 +5,6 @@ import ( "strings" "git.kill0.net/chill9/beepboop/bot" - "git.kill0.net/chill9/beepboop/command" "git.kill0.net/chill9/beepboop/lib" "github.com/bwmarrin/discordgo" @@ -14,7 +13,7 @@ import ( type ( ReactionHandler struct { - config bot.Config + Config bot.Config } ) @@ -23,7 +22,7 @@ func NewReactionHandler() *ReactionHandler { } func (h *ReactionHandler) SetConfig(config bot.Config) { - h.config = config + h.Config = config } func (h *ReactionHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { @@ -31,8 +30,8 @@ func (h *ReactionHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreat return } - emojis := h.config.Handler.Reaction.Emojis - channels := h.config.Handler.Reaction.Channels + emojis := h.Config.Handler.Reaction.Emojis + channels := h.Config.Handler.Reaction.Channels if len(emojis) == 0 { log.Warning("emoji list is empty") @@ -50,7 +49,7 @@ func (h *ReactionHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreat for _, a := range m.Attachments { if strings.HasPrefix(a.ContentType, "image/") { - for i := 1; i <= command.RandInt(1, len(emojis)); i++ { + for i := 1; i <= lib.RandInt(1, len(emojis)); i++ { r := emojis[rand.Intn(len(emojis))] s.MessageReactionAdd(m.ChannelID, m.ID, r) } @@ -58,7 +57,7 @@ func (h *ReactionHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreat } for range m.Embeds { - for i := 1; i <= command.RandInt(1, len(emojis)); i++ { + for i := 1; i <= lib.RandInt(1, len(emojis)); i++ { r := emojis[rand.Intn(len(emojis))] s.MessageReactionAdd(m.ChannelID, m.ID, r) } diff --git a/command/time.go b/bot/handler/time.go similarity index 98% rename from command/time.go rename to bot/handler/time.go index 3916192..e2d87d4 100644 --- a/command/time.go +++ b/bot/handler/time.go @@ -1,4 +1,4 @@ -package command +package handler import ( "fmt" diff --git a/command/version.go b/bot/handler/version.go similarity index 86% rename from command/version.go rename to bot/handler/version.go index b8b6285..8964c55 100644 --- a/command/version.go +++ b/bot/handler/version.go @@ -1,10 +1,11 @@ -package command +package handler import ( "fmt" "runtime" "git.kill0.net/chill9/beepboop/bot" + "git.kill0.net/chill9/beepboop/lib" "github.com/bwmarrin/discordgo" ) @@ -34,7 +35,7 @@ func (h *VersionHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate return } - if !HasCommand(m.Content, h.config.Prefix, h.Name) { + if !lib.HasCommand(m.Content, h.config.Prefix, h.Name) { return } diff --git a/bot/handler/weather.go b/bot/handler/weather.go new file mode 100644 index 0000000..6ddedf4 --- /dev/null +++ b/bot/handler/weather.go @@ -0,0 +1,161 @@ +package handler + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + "git.kill0.net/chill9/beepboop/bot" + "git.kill0.net/chill9/beepboop/lib" + "github.com/bwmarrin/discordgo" + log "github.com/sirupsen/logrus" +) + +const ( + OpenWeatherMapURI = "https://api.openweathermap.org" +) + +var ( + EndpointWeather = lib.BuildURI(OpenWeatherMapURI, "/data/2.5/weather") +) + +type ( + WeatherHandler struct { + Config bot.Config + } + + Temperature float32 + + Weather struct { + Main struct { + Temp Temperature `json:"temp"` + FeelsLike Temperature `json:"feels_like"` + TempMin Temperature `json:"temp_min"` + TempMax Temperature `json:"temp_max"` + Pressure float32 `json:"pressure"` + Humidity float32 `json:"humidity"` + } `json:"main"` + + Coord struct { + Lon float32 `json:"lon"` + Lat float32 `json:"lat"` + } `json:"coord"` + + Rain struct { + H1 float32 `json:"1h"` + H3 float32 `json:"3h"` + } `json:"rain"` + } + + WeatherError struct { + Message string `json:"message"` + } +) + +func (t *Temperature) Kelvin() float32 { + return float32(*t) +} + +func (t *Temperature) Fahrenheit() float32 { + return ((float32(*t) - 273.15) * (9.0 / 5)) + 32 +} + +func (t *Temperature) Celcius() float32 { + return float32(*t) - 273.15 +} + +func NewWeatherHandler() *WeatherHandler { + return new(WeatherHandler) +} + +func (h *WeatherHandler) SetConfig(config bot.Config) { + h.Config = config +} + +func (h *WeatherHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { + var ( + loc string + w Weather + werr WeatherError + ) + + if m.Author.ID == s.State.User.ID { + return + } + + if !strings.HasPrefix(m.Content, "!weather") { + return + } + + x := strings.SplitN(m.Content, " ", 2) + + if len(x) != 2 { + s.ChannelMessageSend(m.ChannelID, "help: `!weather ,,`") + return + } + + loc = x[1] + + if h.Config.OpenWeatherMapToken == "" { + log.Error("OpenWeather token is not set") + return + } + + req, err := http.NewRequest("GET", EndpointWeather, nil) + if err != nil { + log.Errorf("failed to create new request: %s", err) + return + } + + q := req.URL.Query() + q.Add("q", loc) + q.Add("appid", h.Config.OpenWeatherMapToken) + req.URL.RawQuery = q.Encode() + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Errorf("HTTP request failed: %s", err) + return + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Errorf("reading HTTP response failed: %s", err) + return + } + + if resp.StatusCode != 200 { + err = json.Unmarshal(body, &werr) + if err != nil { + log.Debugf("%s\n", body) + log.Errorf("unmarshaling JSON failed: %s", err) + return + } + log.Warnf("error: (%s) %s", resp.Status, werr.Message) + return + } + + log.Debugf("weather requested for '%s'\n", loc) + + err = json.Unmarshal(body, &w) + if err != nil { + log.Debugf("%s\n", body) + log.Errorf("unmarshaling JSON failed: %s", err) + return + } + + log.Debugf("weather returned for '%s': %+v\n", loc, w) + + s.ChannelMessageSend(m.ChannelID, fmt.Sprintf( + "%s (%.1f, %.1f) — C:%.1f F:%.1f K:%.1f", + loc, + w.Coord.Lat, + w.Coord.Lon, + w.Main.Temp.Celcius(), + w.Main.Temp.Fahrenheit(), + w.Main.Temp.Kelvin(), + )) +} diff --git a/cmd/bb/main.go b/cmd/bb/main.go index ebd05e1..4e339ee 100644 --- a/cmd/bb/main.go +++ b/cmd/bb/main.go @@ -11,8 +11,7 @@ import ( "syscall" "git.kill0.net/chill9/beepboop/bot" - handler "git.kill0.net/chill9/beepboop/bot/handlers" - "git.kill0.net/chill9/beepboop/command" + "git.kill0.net/chill9/beepboop/bot/handler" "git.kill0.net/chill9/beepboop/lib" "github.com/bwmarrin/discordgo" @@ -24,14 +23,14 @@ import ( var ( C bot.Config - handlers []command.CommandHandler = []command.CommandHandler{ - command.NewCoinHandler(), - command.NewPingHandler(), - command.NewRollHandler(), - command.NewRouletteHandler(), - command.NewTimeHandler(), - command.NewVersionHandler("version"), - command.NewWeatherHandler(), + handlers []bot.MessageCreateHandler = []bot.MessageCreateHandler{ + handler.NewCoinHandler(), + handler.NewPingHandler(), + handler.NewRollHandler(), + handler.NewRouletteHandler(), + handler.NewTimeHandler(), + handler.NewVersionHandler("version"), + handler.NewWeatherHandler(), handler.NewReactionHandler(), } ) diff --git a/command/command.go b/command/command.go deleted file mode 100644 index 4ffa05b..0000000 --- a/command/command.go +++ /dev/null @@ -1,11 +0,0 @@ -package command - -import ( - "git.kill0.net/chill9/beepboop/bot" - "github.com/bwmarrin/discordgo" -) - -type CommandHandler interface { - Handle(s *discordgo.Session, m *discordgo.MessageCreate) - SetConfig(config bot.Config) -} diff --git a/command/util.go b/command/util.go deleted file mode 100644 index 8b4a1f7..0000000 --- a/command/util.go +++ /dev/null @@ -1,63 +0,0 @@ -package command - -import ( - "math/rand" - "net/url" - "path" - "strconv" - "strings" -) - -func RandInt(min int, max int) int { - return rand.Intn(max-min+1) + min -} - -func JoinInt(a []int, sep string) string { - var b []string - - b = make([]string, len(a)) - - for i, v := range a { - b[i] = strconv.Itoa(v) - } - - return strings.Join(b, sep) -} - -func SumInt(a []int) int { - var sum int - for _, v := range a { - sum += v - } - return sum -} - -func Itob(v int) bool { - if v == 1 { - return true - } - - return false -} - -func BuildURI(rawuri, rawpath string) string { - u, _ := url.Parse(rawuri) - u.Path = path.Join(u.Path, rawpath) - return u.String() -} - -func HasCommand(s, prefix, cmd string) bool { - if len(s) < 2 { - return false - } - - if string(s[0]) != prefix { - return false - } - - if s[1:] == cmd { - return true - } - - return false -} diff --git a/lib/common.go b/lib/common.go index 61bdd83..be22fcd 100644 --- a/lib/common.go +++ b/lib/common.go @@ -1,5 +1,12 @@ package lib +import ( + "net/url" + "path" + "strconv" + "strings" +) + func Contains[T comparable](s []T, v T) bool { for _, x := range s { if x == v { @@ -8,3 +15,53 @@ func Contains[T comparable](s []T, v T) bool { } return false } + +func JoinInt(a []int, sep string) string { + var b []string + + b = make([]string, len(a)) + + for i, v := range a { + b[i] = strconv.Itoa(v) + } + + return strings.Join(b, sep) +} + +func SumInt(a []int) int { + var sum int + for _, v := range a { + sum += v + } + return sum +} + +func Itob(v int) bool { + if v == 1 { + return true + } + + return false +} + +func BuildURI(rawuri, rawpath string) string { + u, _ := url.Parse(rawuri) + u.Path = path.Join(u.Path, rawpath) + return u.String() +} + +func HasCommand(s, prefix, cmd string) bool { + if len(s) < 2 { + return false + } + + if string(s[0]) != prefix { + return false + } + + if s[1:] == cmd { + return true + } + + return false +} diff --git a/lib/rand.go b/lib/rand.go index 0a7f067..c391851 100644 --- a/lib/rand.go +++ b/lib/rand.go @@ -33,3 +33,7 @@ func SeedMathRand() error { return err } + +func RandInt(min int, max int) int { + return rand.Intn(max-min+1) + min +}