Compare commits
	
		
			60 Commits
		
	
	
		
			6c0000d409
			...
			develop
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a0fc17fd89 | |||
| 7a0c90a5f7 | |||
| b8b994157e | |||
| 6139db889d | |||
| 12372522e9 | |||
| a3137e5276 | |||
| 7b3368eea4 | |||
| 5a141be534 | |||
| 49000133d8 | |||
| 756aaf2379 | |||
| 6bd4744745 | |||
| 211f963b87 | |||
| 04aef2f0e4 | |||
| 9221a218b9 | |||
| a551a10e59 | |||
| d8a28fb211 | |||
| 2ac0df3494 | |||
| a151b08142 | |||
| 7ff6e74148 | |||
| b419cfde69 | |||
| 534b3e5fcd | |||
| 446ac616bf | |||
| e2032942ca | |||
| 31cf6f6c9a | |||
| 5651df37ef | |||
| 8606ed0200 | |||
| 3efa3fb5a2 | |||
| 139b32094e | |||
| 4068a4ff06 | |||
| cb9d8e194f | |||
| f3145d8c1d | |||
| 4c5849daae | |||
| c59c95c47a | |||
| 0345b1cba1 | |||
| a1d612abc0 | |||
| 0f25b75fe4 | |||
| d3b95c693b | |||
| 043738668b | |||
| 579921a975 | |||
| 4a024e98f2 | |||
| 9b33684d60 | |||
| cd46fcb60d | |||
| 33bf5eaff2 | |||
| 0e4680eef2 | |||
| 6a389be6a7 | |||
| 35c72daff4 | |||
| fa6f06f639 | |||
| 18d568b5c3 | |||
| e4d6a3fdff | |||
| 7f69e6e1c2 | |||
| 53a6efa13e | |||
| fbd655b2da | |||
| 7d071ebe0b | |||
| af10ef4dc5 | |||
| 424b191ebc | |||
| c01db1abf1 | |||
| ac6d30e085 | |||
| 886a447082 | |||
| 69aee2e699 | |||
| ce689146de | 
							
								
								
									
										13
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| kind: pipeline | ||||
| type: docker | ||||
| name: default | ||||
|  | ||||
| steps: | ||||
|   - name: lint | ||||
|     image: golangci/golangci-lint | ||||
|     commands: | ||||
|       - golangci-lint run | ||||
|   - name: build | ||||
|     image: golang | ||||
|     commands: | ||||
|       - go build ./cmd/bb | ||||
							
								
								
									
										4
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| --- | ||||
| linters: | ||||
|   disable: | ||||
|     - errcheck | ||||
| @@ -6,4 +6,6 @@ RUN CGO_ENABLED=0 go build ./cmd/bb | ||||
| FROM scratch AS bin | ||||
| COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ | ||||
| COPY --from=build /src/bb / | ||||
| ADD https://raw.githubusercontent.com/golang/go/master/lib/time/zoneinfo.zip /zoneinfo.zip | ||||
| ENV ZONEINFO /zoneinfo.zip | ||||
| ENTRYPOINT ["/bb"] | ||||
|   | ||||
							
								
								
									
										239
									
								
								bot/bot.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								bot/bot.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,239 @@ | ||||
| package bot | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"syscall" | ||||
|  | ||||
| 	"git.kill0.net/chill9/beepboop/command" | ||||
| 	"git.kill0.net/chill9/beepboop/config" | ||||
| 	"git.kill0.net/chill9/beepboop/handler" | ||||
| 	"git.kill0.net/chill9/beepboop/lib" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"github.com/spf13/pflag" | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| var C *config.Config | ||||
|  | ||||
| type ( | ||||
| 	Bot struct { | ||||
| 		config   *config.Config | ||||
| 		session  *discordgo.Session | ||||
| 		commands map[string]*Command | ||||
| 	} | ||||
|  | ||||
| 	Command struct { | ||||
| 		Name  string | ||||
| 		Func  CommandFunc | ||||
| 		NArgs int | ||||
| 	} | ||||
|  | ||||
| 	CommandFunc func(args []string, s *discordgo.Session, m *discordgo.MessageCreate) error | ||||
|  | ||||
| 	MessageHandler func(s *discordgo.Session, m *discordgo.MessageCreate) | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	pflag.Bool("debug", false, "enable debug mode") | ||||
| 	pflag.Parse() | ||||
|  | ||||
| 	viper.BindPFlags(pflag.CommandLine) | ||||
| } | ||||
|  | ||||
| func NewBot(config *config.Config, s *discordgo.Session) *Bot { | ||||
| 	return &Bot{ | ||||
| 		session:  s, | ||||
| 		commands: make(map[string]*Command), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (b *Bot) AddHandler(handler interface{}) func() { | ||||
| 	return b.session.AddHandler(handler) | ||||
| } | ||||
|  | ||||
| func (b *Bot) AddCommand(cmd *Command) { | ||||
| 	b.commands[cmd.Name] = cmd | ||||
| } | ||||
|  | ||||
| func (b *Bot) GetCommand(name string) (*Command, bool) { | ||||
| 	cmd, ok := b.commands[name] | ||||
| 	return cmd, ok | ||||
| } | ||||
|  | ||||
| func (b *Bot) CommandHandler(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| 	if m.Author.ID == s.State.User.ID { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !lib.HasCommand(m.Content, b.config.Prefix) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	cmdName, arg := lib.SplitCommandAndArg(m.Content, b.config.Prefix) | ||||
|  | ||||
| 	cmd, ok := b.GetCommand(cmdName) | ||||
| 	if !ok { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	args := lib.SplitArgs(arg, cmd.NArgs) | ||||
|  | ||||
| 	if ok { | ||||
| 		log.Debugf("command: %v, args: %v, nargs: %d", cmd.Name, args, len(args)) | ||||
| 		if err := cmd.Func(args, s, m); err != nil { | ||||
| 			log.Errorf("failed to execute command: %s", err) | ||||
| 		} | ||||
|  | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	log.Warnf("unknown command: %v, args: %v, nargs: %d", cmdName, args, len(args)) | ||||
| 	s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("unknown command: %s", cmdName)) | ||||
| } | ||||
|  | ||||
| func (b *Bot) Init(h *handler.Handlers, ch *command.Handlers) { | ||||
| 	// Register handlers | ||||
| 	b.AddHandler(h.Reaction) | ||||
|  | ||||
| 	// Register commands | ||||
| 	b.AddCommand(&Command{ | ||||
| 		Name: "coin", | ||||
| 		Func: ch.Coin, | ||||
| 	}) | ||||
| 	b.AddCommand(&Command{ | ||||
| 		Name:  "deal", | ||||
| 		Func:  ch.Deal, | ||||
| 		NArgs: 1, | ||||
| 	}) | ||||
| 	b.AddCommand(&Command{ | ||||
| 		Name: "ping", | ||||
| 		Func: ch.Ping, | ||||
| 	}) | ||||
| 	b.AddCommand(&Command{ | ||||
| 		Name:  "roll", | ||||
| 		Func:  ch.Roll, | ||||
| 		NArgs: 1, | ||||
| 	}) | ||||
| 	b.AddCommand(&Command{ | ||||
| 		Name: "roulette", | ||||
| 		Func: ch.Roulette, | ||||
| 	}) | ||||
| 	b.AddCommand(&Command{ | ||||
| 		Name:  "rps", | ||||
| 		Func:  ch.Rps, | ||||
| 		NArgs: 1, | ||||
| 	}) | ||||
| 	b.AddCommand(&Command{ | ||||
| 		Name:  "rpsls", | ||||
| 		Func:  ch.Rpsls, | ||||
| 		NArgs: 1, | ||||
| 	}) | ||||
| 	b.AddCommand(&Command{ | ||||
| 		Name:  "time", | ||||
| 		Func:  ch.Time, | ||||
| 		NArgs: 1, | ||||
| 	}) | ||||
| 	b.AddCommand(&Command{ | ||||
| 		Name: "version", | ||||
| 		Func: ch.Version, | ||||
| 	}) | ||||
| 	b.AddCommand(&Command{ | ||||
| 		Name:  "weather", | ||||
| 		Func:  ch.Weather, | ||||
| 		NArgs: 1, | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func Run() error { | ||||
| 	initConfig() | ||||
| 	go reloadConfig() | ||||
|  | ||||
| 	if err := lib.SeedMathRand(); err != nil { | ||||
| 		log.Warn(err) | ||||
| 	} | ||||
|  | ||||
| 	if C.DiscordToken == "" { | ||||
| 		return errors.New("discord token not set") | ||||
| 	} | ||||
|  | ||||
| 	dg, err := discordgo.New(fmt.Sprintf("Bot %s", C.DiscordToken)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error creating discord session: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	b := NewBot(C, dg) | ||||
| 	b.Init(handler.NewHandlers(C), command.NewHandlers(C)) | ||||
|  | ||||
| 	dg.Identify.Intents = discordgo.IntentsGuildMessages | discordgo.IntentsDirectMessages | ||||
|  | ||||
| 	if err = dg.Open(); err != nil { | ||||
| 		return fmt.Errorf("error opening connection: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	log.Info("The bot is now running. Press CTRL-C to exit.") | ||||
|  | ||||
| 	defer dg.Close() | ||||
|  | ||||
| 	sc := make(chan os.Signal, 1) | ||||
| 	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM) | ||||
| 	<-sc | ||||
|  | ||||
| 	log.Info("Shutting down") | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func initConfig() { | ||||
| 	C = config.NewConfig() | ||||
|  | ||||
| 	viper.SetEnvPrefix("BEEPBOOP") | ||||
| 	viper.AutomaticEnv() | ||||
|  | ||||
| 	viper.SetConfigName("config") | ||||
| 	viper.SetConfigType("toml") | ||||
| 	viper.AddConfigPath(".") | ||||
|  | ||||
| 	viper.SetDefault("debug", false) | ||||
|  | ||||
| 	viper.BindEnv("DISCORD_TOKEN") | ||||
| 	viper.BindEnv("OPEN_WEATHER_MAP_TOKEN") | ||||
|  | ||||
| 	loadConfig() | ||||
| } | ||||
|  | ||||
| func loadConfig() { | ||||
| 	if err := viper.ReadInConfig(); err != nil { | ||||
| 		if _, ok := err.(viper.ConfigFileNotFoundError); !ok { | ||||
| 			log.Fatalf("fatal error config file: %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	log.WithField("filename", viper.ConfigFileUsed()).Info( | ||||
| 		"loaded configuration file", | ||||
| 	) | ||||
|  | ||||
| 	err := viper.Unmarshal(&C) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("unable to decode into struct: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	if viper.GetBool("debug") { | ||||
| 		log.SetLevel(log.DebugLevel) | ||||
| 	} else { | ||||
| 		log.SetLevel(log.InfoLevel) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func reloadConfig() { | ||||
| 	sc := make(chan os.Signal, 1) | ||||
| 	signal.Notify(sc, syscall.SIGHUP) | ||||
| 	for { | ||||
| 		<-sc | ||||
|  | ||||
| 		loadConfig() | ||||
| 	} | ||||
| } | ||||
| @@ -1,41 +0,0 @@ | ||||
| package bot | ||||
|  | ||||
| const ( | ||||
| 	defaultPrefix = "!" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	defaultReactions []string = []string{"👍", "🌶️", "🤣", "😂", "🍆", "🍑", "❤️", "💦", "😍", "💩", "🔥", "🍒", "🎉", "🥳", "🎊"} | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	Config struct { | ||||
| 		Handler             HandlerConfig `mapstructure:"handler"` | ||||
| 		Prefix              string        `mapstructure:"prefix"` | ||||
| 		DiscordToken        string        `mapstructure:"discord_token"` | ||||
| 		OpenWeatherMapToken string        `mapstructure:"open_weather_map_token"` | ||||
| 	} | ||||
|  | ||||
| 	HandlerConfig struct { | ||||
| 		Reaction ReactionConfig `mapstructure:"reaction"` | ||||
| 		Weather  WeatherConfig  `mapstructure:"weather"` | ||||
| 	} | ||||
|  | ||||
| 	ReactionConfig struct { | ||||
| 		Emojis   []string | ||||
| 		Channels []string | ||||
| 	} | ||||
|  | ||||
| 	WeatherConfig struct { | ||||
| 		Token string `mapstructure:"token"` | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func NewConfig() Config { | ||||
| 	var c Config | ||||
|  | ||||
| 	c.Prefix = defaultPrefix | ||||
| 	c.Handler.Reaction.Emojis = defaultReactions | ||||
|  | ||||
| 	return c | ||||
| } | ||||
| @@ -1,10 +0,0 @@ | ||||
| package bot | ||||
|  | ||||
| import ( | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| type MessageCreateHandler interface { | ||||
| 	Handle(s *discordgo.Session, m *discordgo.MessageCreate) | ||||
| 	SetConfig(config Config) | ||||
| } | ||||
							
								
								
									
										101
									
								
								cmd/bb/main.go
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								cmd/bb/main.go
									
									
									
									
									
								
							| @@ -1,110 +1,13 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"flag" | ||||
| 	"fmt" | ||||
|  | ||||
| 	//"log" | ||||
|  | ||||
| 	"os" | ||||
| 	"os/signal" | ||||
| 	"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/lib" | ||||
|  | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| 	"github.com/spf13/pflag" | ||||
| 	"github.com/spf13/viper" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	C bot.Config | ||||
|  | ||||
| 	handlers []command.CommandHandler = []command.CommandHandler{ | ||||
| 		command.NewCoinHandler(), | ||||
| 		command.NewPingHandler(), | ||||
| 		command.NewRollHandler(), | ||||
| 		command.NewRouletteHandler(), | ||||
| 		command.NewTimeHandler(), | ||||
| 		command.NewVersionHandler("version"), | ||||
| 		command.NewWeatherHandler(), | ||||
| 		handler.NewReactionHandler(), | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	setupConfig() | ||||
|  | ||||
| 	lib.SeedMathRand() | ||||
|  | ||||
| 	if C.DiscordToken == "" { | ||||
| 		log.Fatalf("Discord token is not set") | ||||
| 	} | ||||
|  | ||||
| 	dg, err := discordgo.New(fmt.Sprintf("Bot %s", C.DiscordToken)) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("error creating Discord session: %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	for _, h := range handlers { | ||||
| 		h.SetConfig(C) | ||||
| 		dg.AddHandler(h.Handle) | ||||
| 	} | ||||
|  | ||||
| 	dg.Identify.Intents = discordgo.IntentsGuildMessages | ||||
|  | ||||
| 	err = dg.Open() | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("error opening connection: %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	log.Info("The bot is now running. Press CTRL-C to exit.") | ||||
|  | ||||
| 	defer dg.Close() | ||||
|  | ||||
| 	sc := make(chan os.Signal, 1) | ||||
| 	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill) | ||||
| 	<-sc | ||||
|  | ||||
| 	log.Info("Shutting down") | ||||
| } | ||||
|  | ||||
| func setupConfig() { | ||||
| 	var err error | ||||
|  | ||||
| 	C = bot.NewConfig() | ||||
|  | ||||
| 	flag.Bool("debug", false, "enable debug logging") | ||||
| 	pflag.CommandLine.AddGoFlagSet(flag.CommandLine) | ||||
| 	pflag.Parse() | ||||
| 	viper.BindPFlags(pflag.CommandLine) | ||||
|  | ||||
| 	if viper.GetBool("debug") { | ||||
| 		log.SetLevel(log.DebugLevel) | ||||
| 	} | ||||
|  | ||||
| 	viper.SetEnvPrefix("BEEPBOOP") | ||||
| 	viper.AutomaticEnv() | ||||
|  | ||||
| 	viper.SetConfigName("config") | ||||
| 	viper.SetConfigType("toml") | ||||
| 	viper.AddConfigPath(".") | ||||
|  | ||||
| 	err = viper.ReadInConfig() | ||||
|  | ||||
| 	viper.BindEnv("DISCORD_TOKEN") | ||||
| 	viper.BindEnv("OPEN_WEATHER_MAP_TOKEN") | ||||
|  | ||||
| 	if _, ok := err.(viper.ConfigFileNotFoundError); !ok { | ||||
| 		log.Fatalf("fatal error config file: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	err = viper.Unmarshal(&C) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("unable to decode into struct: %v", err) | ||||
| 	if err := bot.Run(); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										29
									
								
								command/coin.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								command/coin.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| package command | ||||
|  | ||||
| import ( | ||||
| 	"git.kill0.net/chill9/beepboop/lib" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| type Coin bool | ||||
|  | ||||
| func (c *Coin) Flip() bool { | ||||
| 	*c = Coin(lib.Itob(lib.RandInt(0, 1))) | ||||
| 	return bool(*c) | ||||
| } | ||||
|  | ||||
| func (h *Handlers) Coin(args []string, s *discordgo.Session, m *discordgo.MessageCreate) error { | ||||
| 	var ( | ||||
| 		c   Coin | ||||
| 		msg string | ||||
| 	) | ||||
|  | ||||
| 	if c.Flip() { | ||||
| 		msg = "heads" | ||||
| 	} else { | ||||
| 		msg = "tails" | ||||
| 	} | ||||
|  | ||||
| 	s.ChannelMessageSend(m.ChannelID, msg) | ||||
| 	return nil | ||||
| } | ||||
| @@ -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) | ||||
| } | ||||
							
								
								
									
										11
									
								
								command/commands.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								command/commands.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| package command | ||||
|  | ||||
| import "git.kill0.net/chill9/beepboop/config" | ||||
|  | ||||
| type Handlers struct { | ||||
| 	config *config.Config | ||||
| } | ||||
|  | ||||
| func NewHandlers(config *config.Config) *Handlers { | ||||
| 	return &Handlers{config: config} | ||||
| } | ||||
							
								
								
									
										83
									
								
								command/deal.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								command/deal.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| package command | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	Card string | ||||
|  | ||||
| 	Deck [52]Card | ||||
| ) | ||||
|  | ||||
| var deck Deck = Deck{ | ||||
| 	"2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "10♣", "J♣", "Q♣", "K♣", "A♣", | ||||
| 	"2♦", "3♦", "4♦", "5♦", "6♦", "7♦", "8♦", "9♦", "10♦", "J♦", "Q♦", "K♦", "A♦", | ||||
| 	"2♥", "3♥", "4♥", "5♥", "6♥", "7♥", "8♥", "9♥", "10♥", "J♥", "Q♥", "K♥", "A♥", | ||||
| 	"2♠", "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠", "A♠", | ||||
| } | ||||
|  | ||||
| func (d *Deck) Deal(n int) ([]Card, error) { | ||||
| 	var ( | ||||
| 		hand []Card | ||||
| 		err  error | ||||
| 	) | ||||
|  | ||||
| 	if n < 1 { | ||||
| 		err = errors.New("number cannot be less than 1") | ||||
| 		return hand, err | ||||
| 	} | ||||
|  | ||||
| 	if n > len(d) { | ||||
| 		err = errors.New("number is greater than cards in the deck") | ||||
| 		return hand, err | ||||
| 	} | ||||
|  | ||||
| 	hand = deck[0:n] | ||||
|  | ||||
| 	return hand, err | ||||
| } | ||||
|  | ||||
| func (h *Handlers) Deal(args []string, s *discordgo.Session, m *discordgo.MessageCreate) error { | ||||
| 	rand.Shuffle(len(deck), func(i, j int) { | ||||
| 		deck[i], deck[j] = deck[j], deck[i] | ||||
| 	}) | ||||
|  | ||||
| 	log.Debugf("%+v", deck) | ||||
|  | ||||
| 	if len(args) != 1 { | ||||
| 		s.ChannelMessageSend(m.ChannelID, "help: `!deal <n>`") | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	n, err := strconv.Atoi(args[0]) | ||||
| 	if err != nil { | ||||
| 		log.Errorf("failed to convert string to int: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	hand, err := deck.Deal(n) | ||||
| 	if err != nil { | ||||
| 		s.ChannelMessageSend(m.ChannelID, fmt.Sprintf("error: %s\n", err)) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	s.ChannelMessageSend(m.ChannelID, JoinCards(hand, " ")) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func JoinCards(h []Card, sep string) string { | ||||
| 	b := make([]string, len(h)) | ||||
|  | ||||
| 	for i, v := range h { | ||||
| 		b[i] = string(v) | ||||
| 	} | ||||
|  | ||||
| 	return strings.Join(b, sep) | ||||
| } | ||||
							
								
								
									
										103
									
								
								command/dice.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								command/dice.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| package command | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"git.kill0.net/chill9/beepboop/lib" | ||||
|  | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	MaxDice  = 100 | ||||
| 	MaxSides = 100 | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	Roll struct { | ||||
| 		N, D, Sum int | ||||
| 		Rolls     []int | ||||
| 		S         string | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func NewRoll(n, d int) *Roll { | ||||
| 	r := new(Roll) | ||||
| 	r.N = n | ||||
| 	r.D = d | ||||
| 	r.S = fmt.Sprintf("%dd%d", r.N, r.D) | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func ParseRoll(roll string) (*Roll, error) { | ||||
| 	var ( | ||||
| 		dice []string | ||||
| 		err  error | ||||
| 		n, d int | ||||
| 	) | ||||
|  | ||||
| 	match, _ := regexp.MatchString(`^(?:\d+)?d\d+$`, roll) | ||||
|  | ||||
| 	if !match { | ||||
| 		return nil, errors.New("invalid roll, use `<n>d<sides>` e.g. `4d6`") | ||||
| 	} | ||||
|  | ||||
| 	dice = strings.Split(roll, "d") | ||||
|  | ||||
| 	if dice[0] == "" { | ||||
| 		n = 1 | ||||
| 	} else { | ||||
| 		n, err = strconv.Atoi(dice[0]) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	d, err = strconv.Atoi(dice[1]) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if n > MaxDice || d > MaxSides { | ||||
| 		return nil, fmt.Errorf("invalid roll, n must be <= %d and sides must be <= %d", MaxDice, MaxSides) | ||||
| 	} | ||||
|  | ||||
| 	return NewRoll(n, d), nil | ||||
| } | ||||
|  | ||||
| func (r *Roll) RollDice() { | ||||
| 	for i := 1; i <= r.N; i++ { | ||||
| 		roll := lib.RandInt(1, r.D) | ||||
| 		r.Rolls = append(r.Rolls, roll) | ||||
| 		r.Sum += roll | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (h *Handlers) Roll(args []string, s *discordgo.Session, m *discordgo.MessageCreate) error { | ||||
| 	var ( | ||||
| 		err       error | ||||
| 		msg, roll string | ||||
| 		r         *Roll | ||||
| 	) | ||||
|  | ||||
| 	roll = args[0] | ||||
|  | ||||
| 	r, err = ParseRoll(roll) | ||||
| 	if err != nil { | ||||
| 		s.ChannelMessageSend(m.ChannelID, err.Error()) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	r.RollDice() | ||||
| 	log.Debugf("rolled dice: %+v", r) | ||||
|  | ||||
| 	msg = fmt.Sprintf("🎲 %s = %d", lib.JoinInt(r.Rolls, " + "), r.Sum) | ||||
|  | ||||
| 	s.ChannelMessageSend(m.ChannelID, msg) | ||||
| 	return nil | ||||
| } | ||||
| @@ -1,37 +1,10 @@ | ||||
| package command | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"git.kill0.net/chill9/beepboop/bot" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	PingHandler struct { | ||||
| 		config bot.Config | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func NewPingHandler() *PingHandler { | ||||
| 	return new(PingHandler) | ||||
| } | ||||
|  | ||||
| func (h *PingHandler) SetConfig(config bot.Config) { | ||||
| 	h.config = config | ||||
| } | ||||
|  | ||||
| func (h *PingHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| 	if m.Author.ID == s.State.User.ID { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !strings.HasPrefix(m.Content, "!ping") { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	log.Debug("received ping") | ||||
|  | ||||
| func (h *Handlers) Ping(args []string, s *discordgo.Session, m *discordgo.MessageCreate) error { | ||||
| 	s.ChannelMessageSend(m.ChannelID, "pong") | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -1,258 +0,0 @@ | ||||
| package command | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"git.kill0.net/chill9/beepboop/bot" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
|  | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	MaxDice         = 100 | ||||
| 	MaxSides        = 100 | ||||
| 	Bullets         = 1 | ||||
| 	GunFireMessage  = "💀🔫" | ||||
| 	GunClickMessage = "😌🔫" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	Roll struct { | ||||
| 		N, D, Sum int | ||||
| 		Rolls     []int | ||||
| 		S         string | ||||
| 	} | ||||
|  | ||||
| 	Coin bool | ||||
|  | ||||
| 	Gun struct { | ||||
| 		C [6]bool | ||||
| 		N int | ||||
| 	} | ||||
|  | ||||
| 	CoinHandler struct { | ||||
| 		config bot.Config | ||||
| 	} | ||||
| 	RollHandler struct { | ||||
| 		config bot.Config | ||||
| 	} | ||||
|  | ||||
| 	RouletteHandler struct { | ||||
| 		config bot.Config | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	gun *Gun | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	gun = NewGun() | ||||
| } | ||||
|  | ||||
| func NewRoll(n, d int) *Roll { | ||||
| 	r := new(Roll) | ||||
| 	r.N = n | ||||
| 	r.D = d | ||||
| 	r.S = fmt.Sprintf("%dd%d", r.N, r.D) | ||||
| 	return r | ||||
| } | ||||
|  | ||||
| func ParseRoll(roll string) (*Roll, error) { | ||||
| 	var ( | ||||
| 		dice []string | ||||
| 		err  error | ||||
| 		n, d int | ||||
| 	) | ||||
|  | ||||
| 	match, _ := regexp.MatchString(`^(?:\d+)?d\d+$`, roll) | ||||
|  | ||||
| 	if !match { | ||||
| 		return nil, errors.New("invalid roll, use `<n>d<sides>` e.g. `4d6`") | ||||
| 	} | ||||
|  | ||||
| 	dice = strings.Split(roll, "d") | ||||
|  | ||||
| 	if dice[0] == "" { | ||||
| 		n = 1 | ||||
| 	} else { | ||||
| 		n, err = strconv.Atoi(dice[0]) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	d, err = strconv.Atoi(dice[1]) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if n > MaxDice || d > MaxSides { | ||||
| 		return nil, fmt.Errorf("invalid roll, n must be <= %d and sides must be <= %d", MaxDice, MaxSides) | ||||
| 	} | ||||
|  | ||||
| 	return NewRoll(n, d), nil | ||||
| } | ||||
|  | ||||
| func (r *Roll) RollDice() { | ||||
| 	for i := 1; i <= r.N; i++ { | ||||
| 		roll := RandInt(1, r.D) | ||||
| 		r.Rolls = append(r.Rolls, roll) | ||||
| 		r.Sum += roll | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Coin) Flip() bool { | ||||
| 	*c = Coin(Itob(RandInt(0, 1))) | ||||
| 	return bool(*c) | ||||
| } | ||||
|  | ||||
| func NewGun() *Gun { | ||||
| 	return new(Gun) | ||||
| } | ||||
|  | ||||
| func (g *Gun) Load(n int) { | ||||
| 	g.N = 0 | ||||
| 	for i := 1; i <= n; { | ||||
| 		x := RandInt(0, len(g.C)-1) | ||||
| 		if g.C[x] == false { | ||||
| 			g.C[x] = true | ||||
| 			i++ | ||||
| 		} else { | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (g *Gun) Fire() bool { | ||||
| 	if g.C[g.N] { | ||||
| 		g.C[g.N] = false | ||||
| 		g.N++ | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	g.N++ | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (g *Gun) IsEmpty() bool { | ||||
| 	for _, v := range g.C { | ||||
| 		if v == true { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func NewRollHandler() *RollHandler { | ||||
| 	return new(RollHandler) | ||||
| } | ||||
|  | ||||
| func (h *RollHandler) SetConfig(config bot.Config) { | ||||
| 	h.config = config | ||||
| } | ||||
|  | ||||
| func (h *RollHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| 	var ( | ||||
| 		err       error | ||||
| 		msg, roll string | ||||
| 		r         *Roll | ||||
| 	) | ||||
|  | ||||
| 	if m.Author.ID == s.State.User.ID { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !strings.HasPrefix(m.Content, "!roll") { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	x := strings.Split(m.Content, " ") | ||||
|  | ||||
| 	if len(x) != 2 { | ||||
| 		s.ChannelMessageSend(m.ChannelID, "help: `!roll <n>d<s>`") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	roll = x[1] | ||||
|  | ||||
| 	r, err = ParseRoll(roll) | ||||
| 	if err != nil { | ||||
| 		s.ChannelMessageSend(m.ChannelID, err.Error()) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	r.RollDice() | ||||
| 	log.Debugf("rolled dice: %+v", r) | ||||
|  | ||||
| 	msg = fmt.Sprintf("🎲 %s = %d", JoinInt(r.Rolls, " + "), r.Sum) | ||||
|  | ||||
| 	s.ChannelMessageSend(m.ChannelID, msg) | ||||
| } | ||||
|  | ||||
| func NewRouletteHandler() *RouletteHandler { | ||||
| 	return new(RouletteHandler) | ||||
| } | ||||
|  | ||||
| func (h *RouletteHandler) SetConfig(config bot.Config) { | ||||
| 	h.config = config | ||||
| } | ||||
|  | ||||
| func (h *RouletteHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| 	if m.Author.ID == s.State.User.ID { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !strings.HasPrefix(m.Content, "!roulette") { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if gun.IsEmpty() { | ||||
| 		gun.Load(Bullets) | ||||
| 		log.Debugf("reloading gun: %+v\n", gun) | ||||
| 	} | ||||
|  | ||||
| 	log.Debugf("firing gun: %+v\n", gun) | ||||
| 	if gun.Fire() { | ||||
| 		s.ChannelMessageSend(m.ChannelID, GunFireMessage) | ||||
| 	} else { | ||||
| 		s.ChannelMessageSend(m.ChannelID, GunClickMessage) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func NewCoinHandler() *CoinHandler { | ||||
| 	return new(CoinHandler) | ||||
| } | ||||
|  | ||||
| func (h *CoinHandler) SetConfig(config bot.Config) { | ||||
| 	h.config = config | ||||
| } | ||||
|  | ||||
| func (h *CoinHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| 	var ( | ||||
| 		c   Coin | ||||
| 		msg string | ||||
| 	) | ||||
|  | ||||
| 	if m.Author.ID == s.State.User.ID { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !strings.HasPrefix(m.Content, "!coin") { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if c.Flip() { | ||||
| 		msg = "heads" | ||||
| 	} else { | ||||
| 		msg = "tails" | ||||
| 	} | ||||
|  | ||||
| 	s.ChannelMessageSend(m.ChannelID, msg) | ||||
| } | ||||
							
								
								
									
										82
									
								
								command/roulette.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								command/roulette.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| package command | ||||
|  | ||||
| import ( | ||||
| 	"git.kill0.net/chill9/beepboop/lib" | ||||
|  | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	Bullets         = 1 | ||||
| 	GunFireMessage  = "💀🔫" | ||||
| 	GunClickMessage = "😌🔫" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	Gun struct { | ||||
| 		C [6]bool | ||||
| 		N int | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	gun *Gun | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	gun = NewGun() | ||||
| } | ||||
|  | ||||
| func NewGun() *Gun { | ||||
| 	return new(Gun) | ||||
| } | ||||
|  | ||||
| func (g *Gun) Load(n int) { | ||||
| 	g.N = 0 | ||||
| 	for i := 1; i <= n; { | ||||
| 		x := lib.RandInt(0, len(g.C)-1) | ||||
| 		if !g.C[x] { | ||||
| 			g.C[x] = true | ||||
| 			i++ | ||||
| 		} else { | ||||
| 			continue | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (g *Gun) Fire() bool { | ||||
| 	if g.C[g.N] { | ||||
| 		g.C[g.N] = false | ||||
| 		g.N++ | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	g.N++ | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func (g *Gun) IsEmpty() bool { | ||||
| 	for _, v := range g.C { | ||||
| 		if v { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (h *Handlers) Roulette(args []string, s *discordgo.Session, m *discordgo.MessageCreate) error { | ||||
| 	if gun.IsEmpty() { | ||||
| 		gun.Load(Bullets) | ||||
| 		log.Debugf("reloading gun: %+v\n", gun) | ||||
| 	} | ||||
|  | ||||
| 	log.Debugf("firing gun: %+v\n", gun) | ||||
| 	if gun.Fire() { | ||||
| 		s.ChannelMessageSend(m.ChannelID, GunFireMessage) | ||||
| 	} else { | ||||
| 		s.ChannelMessageSend(m.ChannelID, GunClickMessage) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										34
									
								
								command/rps.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								command/rps.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| package command | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"git.kill0.net/chill9/beepboop/lib/rps" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| func (h *Handlers) Rps(args []string, s *discordgo.Session, m *discordgo.MessageCreate) error { | ||||
| 	if len(args) != 1 { | ||||
| 		s.ChannelMessageSend( | ||||
| 			m.ChannelID, "help: `!rps (rock | paper | scissors)`", | ||||
| 		) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	pc := strings.ToLower(args[0]) // player's choice | ||||
|  | ||||
| 	g := rps.NewGame(rps.RulesRps, rps.EmojiMapRps) | ||||
|  | ||||
| 	bc := g.Rand() // bot's choice | ||||
|  | ||||
| 	out, err := g.Play(bc, pc) | ||||
| 	if _, ok := err.(rps.InvalidChoiceError); ok { | ||||
| 		s.ChannelMessageSend( | ||||
| 			m.ChannelID, "help: `!rps (rock | paper | scissors)`", | ||||
| 		) | ||||
| 	} | ||||
|  | ||||
| 	s.ChannelMessageSend(m.ChannelID, out) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										34
									
								
								command/rpsls.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								command/rpsls.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| package command | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"git.kill0.net/chill9/beepboop/lib/rps" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| func (h *Handlers) Rpsls(args []string, s *discordgo.Session, m *discordgo.MessageCreate) error { | ||||
| 	if len(args) != 1 { | ||||
| 		s.ChannelMessageSend( | ||||
| 			m.ChannelID, "help: `!rps (rock | paper | scissors | lizard | spock)`", | ||||
| 		) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	pc := strings.ToLower(args[0]) // player's choice | ||||
|  | ||||
| 	g := rps.NewGame(rps.RulesRpsls, rps.EmojiMapRpsls) | ||||
|  | ||||
| 	bc := g.Rand() // bot's choice | ||||
|  | ||||
| 	out, err := g.Play(bc, pc) | ||||
| 	if _, ok := err.(rps.InvalidChoiceError); ok { | ||||
| 		s.ChannelMessageSend( | ||||
| 			m.ChannelID, "help: `!rps (rock | paper | scissors | lizard | spock)`", | ||||
| 		) | ||||
| 	} | ||||
|  | ||||
| 	s.ChannelMessageSend(m.ChannelID, out) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
| @@ -2,58 +2,27 @@ package command | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.kill0.net/chill9/beepboop/bot" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	TimeHandler struct { | ||||
| 		config bot.Config | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func NewTimeHandler() *TimeHandler { | ||||
| 	return new(TimeHandler) | ||||
| } | ||||
|  | ||||
| func (h *TimeHandler) SetConfig(config bot.Config) { | ||||
| 	h.config = config | ||||
| } | ||||
|  | ||||
| func (h *TimeHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| func (h *Handlers) Time(args []string, s *discordgo.Session, m *discordgo.MessageCreate) error { | ||||
| 	var ( | ||||
| 		t  time.Time | ||||
| 		tz string | ||||
| 	) | ||||
|  | ||||
| 	if m.Author.ID == s.State.User.ID { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !strings.HasPrefix(m.Content, "!time") { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	x := strings.SplitN(m.Content, " ", 2) | ||||
|  | ||||
| 	if len(x) > 2 { | ||||
| 		s.ChannelMessageSend(m.ChannelID, "help: `!time TIMEZONE`") | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	now := time.Now() | ||||
|  | ||||
| 	if len(x) == 2 { | ||||
| 		tz = x[1] | ||||
| 	if len(args) == 1 { | ||||
| 		tz = args[0] | ||||
| 		loc, err := time.LoadLocation(tz) | ||||
| 		if err != nil { | ||||
| 			log.Warnf("failed to load location: %s", err) | ||||
| 			s.ChannelMessageSend(m.ChannelID, err.Error()) | ||||
| 			return | ||||
| 			return nil | ||||
| 		} | ||||
| 		t = now.In(loc) | ||||
| 	} else { | ||||
| @@ -61,4 +30,5 @@ func (h *TimeHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| 	} | ||||
|  | ||||
| 	s.ChannelMessageSend(m.ChannelID, fmt.Sprint(t)) | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
| @@ -4,7 +4,6 @@ import ( | ||||
| 	"fmt" | ||||
| 	"runtime" | ||||
|  | ||||
| 	"git.kill0.net/chill9/beepboop/bot" | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| ) | ||||
|  | ||||
| @@ -12,32 +11,7 @@ const ( | ||||
| 	SourceURI = "https://git.kill0.net/chill9/bb" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	VersionHandler struct { | ||||
| 		config bot.Config | ||||
| 		Name   string | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func NewVersionHandler(s string) *VersionHandler { | ||||
| 	h := new(VersionHandler) | ||||
| 	h.Name = s | ||||
| 	return h | ||||
| } | ||||
|  | ||||
| func (h *VersionHandler) SetConfig(config bot.Config) { | ||||
| 	h.config = config | ||||
| } | ||||
|  | ||||
| func (h *VersionHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| 	if m.Author.ID == s.State.User.ID { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !HasCommand(m.Content, h.config.Prefix, h.Name) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| func (h *Handlers) Version(args []string, s *discordgo.Session, m *discordgo.MessageCreate) error { | ||||
| 	s.ChannelMessageSend(m.ChannelID, fmt.Sprintf( | ||||
| 		"go version: %s\nplatform: %s\nos: %s\nsource: %s\n", | ||||
| 		runtime.Version(), | ||||
| @@ -45,4 +19,5 @@ func (h *VersionHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate | ||||
| 		runtime.GOOS, | ||||
| 		SourceURI, | ||||
| 	)) | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
							
								
								
									
										54
									
								
								command/weather.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								command/weather.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | ||||
| package command | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"git.kill0.net/chill9/beepboop/lib/weather" | ||||
|  | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| func (h *Handlers) Weather(args []string, s *discordgo.Session, m *discordgo.MessageCreate) error { | ||||
| 	var ( | ||||
| 		err error | ||||
| 		loc string | ||||
| 		w   weather.Weather | ||||
| 	) | ||||
|  | ||||
| 	if len(args) != 1 { | ||||
| 		s.ChannelMessageSend(m.ChannelID, "help: `!weather <CITY>,<STATE>,<COUNTRY>`") | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	loc = args[0] | ||||
|  | ||||
| 	if h.config.OpenWeatherMapToken == "" { | ||||
| 		log.Error("OpenWeather token is not set") | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	wc := weather.NewClient(h.config.OpenWeatherMapToken) | ||||
|  | ||||
| 	log.Debugf("weather requested for '%s'", loc) | ||||
|  | ||||
| 	w, err = wc.Get(loc) | ||||
| 	if err != nil { | ||||
| 		log.Errorf("weather client error: %v", err) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	log.Debugf("weather returned for '%s': %+v", 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(), | ||||
| 	)) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										58
									
								
								config/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								config/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| package config | ||||
|  | ||||
| const ( | ||||
| 	defaultPrefix = "!" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	defaultReactions []string = []string{ | ||||
| 		"👍", "🌶️", "🤣", "😂", "🍆", "🍑", "❤️", "💦", "😍", "💩", | ||||
| 		"🔥", "🍒", "🎉", "🥳", "🎊", "📉", "📈", "💀", "☠️", | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	Config struct { | ||||
| 		Debug               bool           `mapstructure:"debug"` | ||||
| 		Handler             HandlerConfig  `mapstructure:"handler"` | ||||
| 		Prefix              string         `mapstructure:"prefix"` | ||||
| 		DiscordToken        string         `mapstructure:"discord_token"` | ||||
| 		OpenWeatherMapToken string         `mapstructure:"open_weather_map_token"` | ||||
| 		Mongo               MongoConfig    `mapstructure:"mongo"` | ||||
| 		Redis               RedisConfig    `mapstructure:"redis"` | ||||
| 		Postgres            PostgresConfig `mapstructure:"postgres"` | ||||
| 	} | ||||
|  | ||||
| 	HandlerConfig struct { | ||||
| 		Reaction ReactionConfig `mapstructure:"reaction"` | ||||
| 	} | ||||
|  | ||||
| 	ReactionConfig struct { | ||||
| 		Emojis   []string | ||||
| 		Channels []string | ||||
| 	} | ||||
|  | ||||
| 	MongoConfig struct { | ||||
| 		Uri      string `mapstructure:"uri"` | ||||
| 		Database string `mapstructure:"database"` | ||||
| 	} | ||||
|  | ||||
| 	RedisConfig struct { | ||||
| 		Addr     string `mapstructure:"addr"` | ||||
| 		Password string `mapstructure:"password"` | ||||
| 		DB       int    `mapstructure:"database"` | ||||
| 	} | ||||
|  | ||||
| 	PostgresConfig struct { | ||||
| 		Uri string `mapstructure:"uri"` | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func NewConfig() *Config { | ||||
| 	var c *Config = &Config{} | ||||
|  | ||||
| 	c.Prefix = defaultPrefix | ||||
| 	c.Handler.Reaction.Emojis = defaultReactions | ||||
|  | ||||
| 	return c | ||||
| } | ||||
| @@ -1,3 +1,4 @@ | ||||
| --- | ||||
| version: "3.9" | ||||
| services: | ||||
|   bot: | ||||
| @@ -5,3 +6,6 @@ services: | ||||
|     environment: | ||||
|       - BEEPBOOP_DISCORD_TOKEN | ||||
|       - BEEPBOOP_OPEN_WEATHER_MAP_TOKEN | ||||
|       - BEEPBOOP_DEBUG=true | ||||
|     volumes: | ||||
|       - ./config.toml:/config.toml | ||||
							
								
								
									
										23
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								go.mod
									
									
									
									
									
								
							| @@ -3,29 +3,28 @@ module git.kill0.net/chill9/beepboop | ||||
| go 1.18 | ||||
|  | ||||
| require ( | ||||
| 	github.com/bwmarrin/discordgo v0.25.0 | ||||
| 	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b | ||||
| 	github.com/bwmarrin/discordgo v0.26.1 | ||||
| 	github.com/sirupsen/logrus v1.9.0 | ||||
| 	github.com/spf13/viper v1.12.0 | ||||
| 	github.com/spf13/pflag v1.0.5 | ||||
| 	github.com/spf13/viper v1.13.0 | ||||
| ) | ||||
|  | ||||
| require ( | ||||
| 	github.com/fsnotify/fsnotify v1.5.4 // indirect | ||||
| 	github.com/gorilla/websocket v1.4.2 // indirect | ||||
| 	github.com/gorilla/websocket v1.5.0 // indirect | ||||
| 	github.com/hashicorp/hcl v1.0.0 // indirect | ||||
| 	github.com/magiconair/properties v1.8.6 // indirect | ||||
| 	github.com/mitchellh/mapstructure v1.5.0 // indirect | ||||
| 	github.com/pelletier/go-toml v1.9.5 // indirect | ||||
| 	github.com/pelletier/go-toml/v2 v2.0.1 // indirect | ||||
| 	github.com/spf13/afero v1.8.2 // indirect | ||||
| 	github.com/pelletier/go-toml/v2 v2.0.5 // indirect | ||||
| 	github.com/spf13/afero v1.9.2 // indirect | ||||
| 	github.com/spf13/cast v1.5.0 // indirect | ||||
| 	github.com/spf13/jwalterweatherman v1.1.0 // indirect | ||||
| 	github.com/spf13/pflag v1.0.5 // indirect | ||||
| 	github.com/subosito/gotenv v1.3.0 // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect | ||||
| 	golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect | ||||
| 	github.com/subosito/gotenv v1.4.1 // indirect | ||||
| 	golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect | ||||
| 	golang.org/x/sys v0.0.0-20220907062415-87db552b00fd // indirect | ||||
| 	golang.org/x/text v0.3.7 // indirect | ||||
| 	gopkg.in/ini.v1 v1.66.4 // indirect | ||||
| 	gopkg.in/ini.v1 v1.67.0 // indirect | ||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||
| 	gopkg.in/yaml.v3 v3.0.0 // indirect | ||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||
| ) | ||||
|   | ||||
							
								
								
									
										54
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								go.sum
									
									
									
									
									
								
							| @@ -38,8 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f | ||||
| dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= | ||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||||
| github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= | ||||
| github.com/bwmarrin/discordgo v0.25.0 h1:NXhdfHRNxtwso6FPdzW2i3uBvvU7UIQTghmV2T4nqAs= | ||||
| github.com/bwmarrin/discordgo v0.25.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= | ||||
| github.com/bwmarrin/discordgo v0.26.1 h1:AIrM+g3cl+iYBr4yBxCBp9tD9jR3K7upEjl0d89FRkE= | ||||
| github.com/bwmarrin/discordgo v0.26.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= | ||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= | ||||
| github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= | ||||
| github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | ||||
| @@ -49,6 +49,7 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX | ||||
| github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= | ||||
| github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||
| github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= | ||||
| @@ -56,12 +57,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m | ||||
| github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= | ||||
| github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= | ||||
| github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= | ||||
| github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= | ||||
| github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= | ||||
| github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= | ||||
| github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= | ||||
| github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||||
| github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= | ||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= | ||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||
| github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||
| @@ -98,6 +99,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ | ||||
| github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||
| github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= | ||||
| github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= | ||||
| github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= | ||||
| github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= | ||||
| @@ -116,8 +118,9 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ | ||||
| github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= | ||||
| github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= | ||||
| github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= | ||||
| github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= | ||||
| github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||
| github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= | ||||
| github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||
| github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||
| github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= | ||||
| github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= | ||||
| @@ -129,41 +132,48 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X | ||||
| github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||||
| github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= | ||||
| github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||||
| github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= | ||||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||
| github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= | ||||
| github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= | ||||
| github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= | ||||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||||
| github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= | ||||
| github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= | ||||
| github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= | ||||
| github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= | ||||
| github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= | ||||
| github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= | ||||
| github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= | ||||
| github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= | ||||
| github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= | ||||
| github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= | ||||
| github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= | ||||
| github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= | ||||
| github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= | ||||
| github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= | ||||
| github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= | ||||
| github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= | ||||
| github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= | ||||
| github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= | ||||
| github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= | ||||
| github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= | ||||
| github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= | ||||
| github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= | ||||
| github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||
| github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||
| github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= | ||||
| github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= | ||||
| github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= | ||||
| github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||||
| github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= | ||||
| github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= | ||||
| github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= | ||||
| @@ -179,11 +189,10 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U | ||||
| golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||||
| golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= | ||||
| golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | ||||
| golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= | ||||
| golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= | ||||
| golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | ||||
| @@ -294,7 +303,6 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w | ||||
| golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= | ||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| @@ -303,10 +311,9 @@ golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7w | ||||
| golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= | ||||
| golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U= | ||||
| golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| @@ -459,16 +466,17 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD | ||||
| google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= | ||||
| google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= | ||||
| gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= | ||||
| gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= | ||||
| gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= | ||||
| gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||
| gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= | ||||
| gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= | ||||
| gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
| honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
| honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
| honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= | ||||
|   | ||||
							
								
								
									
										15
									
								
								handler/handlers.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								handler/handlers.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| package handler | ||||
|  | ||||
| import ( | ||||
| 	"git.kill0.net/chill9/beepboop/config" | ||||
| ) | ||||
|  | ||||
| type Handlers struct { | ||||
| 	config *config.Config | ||||
| } | ||||
|  | ||||
| func NewHandlers(config *config.Config) *Handlers { | ||||
| 	return &Handlers{ | ||||
| 		config: config, | ||||
| 	} | ||||
| } | ||||
| @@ -4,29 +4,13 @@ import ( | ||||
| 	"math/rand" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"git.kill0.net/chill9/beepboop/bot" | ||||
| 	"git.kill0.net/chill9/beepboop/command" | ||||
| 	"git.kill0.net/chill9/beepboop/lib" | ||||
| 
 | ||||
| 	"github.com/bwmarrin/discordgo" | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
| 
 | ||||
| type ( | ||||
| 	ReactionHandler struct { | ||||
| 		config bot.Config | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
| func NewReactionHandler() *ReactionHandler { | ||||
| 	return new(ReactionHandler) | ||||
| } | ||||
| 
 | ||||
| func (h *ReactionHandler) SetConfig(config bot.Config) { | ||||
| 	h.config = config | ||||
| } | ||||
| 
 | ||||
| func (h *ReactionHandler) Handle(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| func (h *Handlers) Reaction(s *discordgo.Session, m *discordgo.MessageCreate) { | ||||
| 	if m.Author.ID == s.State.User.ID { | ||||
| 		return | ||||
| 	} | ||||
| @@ -50,7 +34,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 +42,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) | ||||
| 		} | ||||
							
								
								
									
										151
									
								
								lib/common.go
									
									
									
									
									
								
							
							
						
						
									
										151
									
								
								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,147 @@ func Contains[T comparable](s []T, v T) bool { | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func JoinInt(a []int, sep string) 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 { | ||||
| 	return v == 1 | ||||
| } | ||||
|  | ||||
| func BuildURI(rawuri, rawpath string) string { | ||||
| 	u, _ := url.Parse(rawuri) | ||||
| 	u.Path = path.Join(u.Path, rawpath) | ||||
| 	return u.String() | ||||
| } | ||||
|  | ||||
| func HasCommand(s, prefix string) bool { | ||||
| 	s = strings.TrimSpace(s) | ||||
|  | ||||
| 	if len(s) == 0 || len(prefix) == 0 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if !strings.HasPrefix(s, prefix) { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	// remove the command prefix | ||||
| 	s = s[len(prefix):] | ||||
|  | ||||
| 	// multiple assignment trick | ||||
| 	cmd, _ := func() (string, string) { | ||||
| 		x := strings.SplitN(s, " ", 2) | ||||
| 		if len(x) > 1 { | ||||
| 			return x[0], x[1] | ||||
| 		} | ||||
|  | ||||
| 		return x[0], "" | ||||
| 	}() | ||||
|  | ||||
| 	return len(cmd) > 0 | ||||
| } | ||||
|  | ||||
| func ContainsCommand(s, prefix, cmd string) bool { | ||||
| 	s = strings.TrimSpace(s) | ||||
|  | ||||
| 	args := strings.Split(s, " ") | ||||
| 	s = args[0] | ||||
|  | ||||
| 	// a command cannot be less than two characters e.g. !x | ||||
| 	if len(s) < 2 { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if string(s[0]) != prefix { | ||||
| 		return false | ||||
| 	} | ||||
|  | ||||
| 	if strings.HasPrefix(s[1:], cmd) { | ||||
| 		return true | ||||
| 	} | ||||
|  | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| func SplitCommandAndArg(s, prefix string) (cmd string, arg string) { | ||||
| 	s = strings.TrimSpace(s) | ||||
|  | ||||
| 	if !strings.HasPrefix(s, prefix) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// remove the command prefix | ||||
| 	s = s[len(prefix):] | ||||
|  | ||||
| 	// multiple assignment trick | ||||
| 	cmd, arg = func() (string, string) { | ||||
| 		x := strings.SplitN(s, " ", 2) | ||||
| 		if len(x) > 1 { | ||||
| 			return x[0], x[1] | ||||
| 		} | ||||
| 		return x[0], "" | ||||
| 	}() | ||||
|  | ||||
| 	return cmd, arg | ||||
| } | ||||
|  | ||||
| func SplitCommandAndArgs(s, prefix string, n int) (cmd string, args []string) { | ||||
| 	cmd, arg := SplitCommandAndArg(s, prefix) | ||||
|  | ||||
| 	if arg == "" { | ||||
| 		return cmd, []string{} | ||||
| 	} | ||||
|  | ||||
| 	if n == 0 { | ||||
| 		return cmd, strings.Split(arg, " ") | ||||
| 	} | ||||
|  | ||||
| 	return cmd, strings.SplitN(arg, " ", n) | ||||
| } | ||||
|  | ||||
| func SplitArgs(s string, n int) (args []string) { | ||||
| 	if s == "" { | ||||
| 		return []string{} | ||||
| 	} | ||||
|  | ||||
| 	if n > 0 { | ||||
| 		args = strings.SplitN(s, " ", n) | ||||
| 	} else { | ||||
| 		args = strings.Split(s, " ") | ||||
| 	} | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func MapKeys[K comparable, V any](m map[K]V) []K { | ||||
| 	keys := make([]K, 0, len(m)) | ||||
| 	for k := range m { | ||||
| 		keys = append(keys, k) | ||||
| 	} | ||||
| 	return keys | ||||
| } | ||||
|  | ||||
| func MapKey[K comparable, V comparable](m map[K]V, v V) K { | ||||
| 	var r K | ||||
| 	for k := range m { | ||||
| 		if m[k] == v { | ||||
| 			return k | ||||
| 		} | ||||
| 	} | ||||
| 	return r | ||||
| } | ||||
|   | ||||
							
								
								
									
										159
									
								
								lib/common_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								lib/common_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| package lib | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestContainsCommand(t *testing.T) { | ||||
| 	tables := []struct { | ||||
| 		s      string | ||||
| 		prefix string | ||||
| 		cmd    string | ||||
| 		r      bool | ||||
| 	}{ | ||||
| 		{"!command x y", "!", "command", true}, | ||||
| 		{"#command x y", "#", "command", true}, | ||||
| 		{"command x y", "!", "comamnd", false}, | ||||
| 		{"", "", "", false}, | ||||
| 		{"!", "!", "", false}, | ||||
| 		{"! x y", "!", "", false}, | ||||
| 	} | ||||
|  | ||||
| 	for _, table := range tables { | ||||
| 		r := ContainsCommand(table.s, table.prefix, table.cmd) | ||||
| 		if r != table.r { | ||||
| 			t.Errorf("ContainsCommand(%q, %q, %q), got: %t, want: %t", | ||||
| 				table.s, table.prefix, table.cmd, r, table.r, | ||||
| 			) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestHasCommandCommand(t *testing.T) { | ||||
| 	tables := []struct { | ||||
| 		s      string | ||||
| 		prefix string | ||||
| 		want   bool | ||||
| 	}{ | ||||
| 		{"!command", "!", true}, | ||||
| 		{"!command x y", "!", true}, | ||||
| 		{"!c x y", "!", true}, | ||||
| 		{"! x y", "!", false}, | ||||
| 		{"hey guy", "!", false}, | ||||
| 		{"hey", "!", false}, | ||||
| 		{"hey", "", false}, | ||||
| 		{"", "!", false}, | ||||
| 		{"", "", false}, | ||||
| 	} | ||||
|  | ||||
| 	for _, table := range tables { | ||||
| 		if got, want := HasCommand(table.s, table.prefix), table.want; got != want { | ||||
| 			t.Errorf( | ||||
| 				"s: %s, prefix: %s, got: %t, want: %t", | ||||
| 				table.s, table.prefix, got, want, | ||||
| 			) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSplitCommandAndArg(t *testing.T) { | ||||
| 	tables := []struct { | ||||
| 		s       string | ||||
| 		prefix  string | ||||
| 		wantCmd string | ||||
| 		wantArg string | ||||
| 	}{ | ||||
| 		{"!command x y", "!", "command", "x y"}, | ||||
| 		{"!command", "!", "command", ""}, | ||||
| 		{"hey man", "!", "", ""}, | ||||
| 	} | ||||
|  | ||||
| 	for _, table := range tables { | ||||
| 		gotCmd, gotArg := SplitCommandAndArg(table.s, table.prefix) | ||||
| 		if gotCmd != table.wantCmd { | ||||
| 			t.Errorf("got: %s, want: %s", gotCmd, table.wantCmd) | ||||
| 		} | ||||
| 		if gotArg != table.wantArg { | ||||
| 			t.Errorf("got: %+v, want: %+v", gotArg, table.wantArg) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSplitCommandAndArgs(t *testing.T) { | ||||
| 	tables := []struct { | ||||
| 		s        string | ||||
| 		prefix   string | ||||
| 		n        int | ||||
| 		wantCmd  string | ||||
| 		wantArgs []string | ||||
| 	}{ | ||||
| 		{"!command x y", "!", 2, "command", []string{"x", "y"}}, | ||||
| 		{"!command x y z", "!", 2, "command", []string{"x", "y z"}}, | ||||
| 		{"!command", "!", 1, "command", []string{}}, | ||||
| 		{"hey man", "!", 1, "", []string{}}, | ||||
| 	} | ||||
| 	for _, table := range tables { | ||||
| 		gotCmd, gotArgs := SplitCommandAndArgs(table.s, table.prefix, table.n) | ||||
| 		if gotCmd != table.wantCmd { | ||||
| 			t.Errorf("got: %s, want: %s", gotCmd, table.wantCmd) | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(gotArgs, table.wantArgs) { | ||||
| 			t.Errorf("got: %#v, want: %#v", gotArgs, table.wantArgs) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestSplitArgs(t *testing.T) { | ||||
| 	tables := []struct { | ||||
| 		s    string | ||||
| 		n    int | ||||
| 		want []string | ||||
| 	}{ | ||||
| 		{"a b c", 0, []string{"a", "b", "c"}}, | ||||
| 		{"a b c", 1, []string{"a b c"}}, | ||||
| 		{"a b c", 2, []string{"a", "b c"}}, | ||||
| 		{"a b c", 3, []string{"a", "b", "c"}}, | ||||
| 		{"a b c", 4, []string{"a", "b", "c"}}, | ||||
| 		{"", 0, []string{}}, | ||||
| 	} | ||||
| 	for _, table := range tables { | ||||
| 		if got, want := SplitArgs(table.s, table.n), table.want; !reflect.DeepEqual(got, want) { | ||||
| 			t.Errorf("got: %#v, want: %#v", got, want) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestMapKeys(t *testing.T) { | ||||
| 	tables := []struct { | ||||
| 		m    map[string]int | ||||
| 		want []string | ||||
| 	}{ | ||||
| 		{map[string]int{"a": 0, "b": 1, "c": 3}, []string{"a", "b", "c"}}, | ||||
| 	} | ||||
|  | ||||
| 	for _, table := range tables { | ||||
| 		if got, want := MapKeys(table.m), table.want; !reflect.DeepEqual(got, want) { | ||||
| 			t.Errorf("got: %#v, want: %#v", got, want) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestMapKey(t *testing.T) { | ||||
| 	tables := []struct { | ||||
| 		m    map[string]int | ||||
| 		n    int | ||||
| 		want string | ||||
| 	}{ | ||||
| 		{map[string]int{"a": 0, "b": 1, "c": 2}, 0, "a"}, | ||||
| 		{map[string]int{"a": 0, "b": 1, "c": 2}, 1, "b"}, | ||||
| 		{map[string]int{"a": 0, "b": 1, "c": 2}, 2, "c"}, | ||||
| 		{map[string]int{"a": 0, "b": 1, "c": 2, "d": 0}, 0, "a"}, | ||||
| 	} | ||||
|  | ||||
| 	for _, table := range tables { | ||||
| 		if got, want := MapKey(table.m, table.n), table.want; got != want { | ||||
| 			t.Errorf("got: %#v, want: %#v", got, want) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										22
									
								
								lib/rand.go
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								lib/rand.go
									
									
									
									
									
								
							| @@ -33,3 +33,25 @@ func SeedMathRand() error { | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func RandInt(min int, max int) int { | ||||
| 	return rand.Intn(max-min+1) + min | ||||
| } | ||||
|  | ||||
| func MapRand[K comparable, V any](m map[K]V) V { | ||||
| 	n := rand.Intn(len(m)) | ||||
| 	i := 0 | ||||
| 	for _, v := range m { | ||||
| 		if i == n { | ||||
| 			return v | ||||
| 		} | ||||
| 		i++ | ||||
| 	} | ||||
| 	panic("unreachable") | ||||
| } | ||||
|  | ||||
| func MapRandKey[K comparable, V any](m map[K]V) K { | ||||
| 	keys := MapKeys(m) | ||||
| 	n := rand.Intn(len(m)) | ||||
| 	return keys[n] | ||||
| } | ||||
|   | ||||
							
								
								
									
										118
									
								
								lib/rps/rps.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								lib/rps/rps.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| package rps | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"git.kill0.net/chill9/beepboop/lib" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	Game struct { | ||||
| 		rules    [][]string | ||||
| 		emojiMap map[string]string | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| type InvalidChoiceError struct { | ||||
| 	s string | ||||
| } | ||||
|  | ||||
| func (e InvalidChoiceError) Error() string { | ||||
| 	return fmt.Sprintf("%q is an invalid choice", e.s) | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	RulesRps [][]string = [][]string{ | ||||
| 		{"rock", "scissors", "crushes"}, | ||||
| 		{"paper", "rock", "covers"}, | ||||
| 		{"scissors", "paper", "cuts"}, | ||||
| 	} | ||||
|  | ||||
| 	EmojiMapRps map[string]string = map[string]string{ | ||||
| 		"rock":     "🪨️", | ||||
| 		"paper":    "📝", | ||||
| 		"scissors": "✂️", | ||||
| 	} | ||||
|  | ||||
| 	RulesRpsls [][]string = [][]string{ | ||||
| 		{"rock", "scissors", "crushes"}, | ||||
| 		{"rock", "lizard", "crushes"}, | ||||
| 		{"paper", "rock", "covers"}, | ||||
| 		{"paper", "spock", "disproves"}, | ||||
| 		{"scissors", "paper", "cuts"}, | ||||
| 		{"scissors", "lizard", "decapitates"}, | ||||
| 		{"lizard", "paper", "eats"}, | ||||
| 		{"lizard", "spock", "poisons"}, | ||||
| 		{"spock", "scissors", "smashes"}, | ||||
| 		{"spock", "rock", "vaporizes"}, | ||||
| 	} | ||||
|  | ||||
| 	EmojiMapRpsls map[string]string = map[string]string{ | ||||
| 		"rock":     "🪨️", | ||||
| 		"paper":    "📝", | ||||
| 		"scissors": "✂️", | ||||
| 		"lizard":   "🦎", | ||||
| 		"spock":    "🖖", | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| func NewGame(rules [][]string, emojiMap map[string]string) *Game { | ||||
| 	return &Game{rules: rules, emojiMap: emojiMap} | ||||
| } | ||||
|  | ||||
| func (g *Game) Rand() string { | ||||
| 	return lib.MapRandKey(g.emojiMap) | ||||
| } | ||||
|  | ||||
| func (g *Game) Play(c1, c2 string) (string, error) { | ||||
| 	var b strings.Builder | ||||
|  | ||||
| 	if !g.Valid(c1) { | ||||
| 		return "", InvalidChoiceError{s: c1} | ||||
| 	} | ||||
| 	if !g.Valid(c2) { | ||||
| 		return "", InvalidChoiceError{s: c2} | ||||
| 	} | ||||
|  | ||||
| 	fmt.Fprintf(&b, "%s v %s: ", g.emojiMap[c1], g.emojiMap[c2]) | ||||
|  | ||||
| 	for _, rule := range g.rules { | ||||
| 		verb := rule[2] | ||||
|  | ||||
| 		if c1 == c2 { | ||||
| 			fmt.Fprintf(&b, "draw") | ||||
| 			return b.String(), nil | ||||
| 		} | ||||
|  | ||||
| 		if c1 == rule[0] && c2 == rule[1] { | ||||
| 			fmt.Fprintf(&b, "%s %s %s", c1, verb, c2) | ||||
| 			return b.String(), nil | ||||
| 		} else if c2 == rule[0] && c1 == rule[1] { | ||||
| 			fmt.Fprintf(&b, "%s %s %s", c2, verb, c1) | ||||
| 			return b.String(), nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return b.String(), nil | ||||
| } | ||||
|  | ||||
| func (g *Game) Valid(c string) bool { | ||||
| 	_, ok := g.emojiMap[c] | ||||
| 	return ok | ||||
| } | ||||
|  | ||||
| func Play(rules [][]string, c1, c2 string) string { | ||||
| 	for _, rule := range rules { | ||||
| 		if c1 == c2 { | ||||
| 			return "draw" | ||||
| 		} | ||||
|  | ||||
| 		if c1 == rule[0] && c2 == rule[1] { | ||||
| 			return fmt.Sprintf("%s %s %s", c1, rule[2], c2) | ||||
| 		} else if c2 == rule[0] && c1 == rule[1] { | ||||
| 			return fmt.Sprintf("%s %s %s", c2, rule[2], c1) | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
							
								
								
									
										48
									
								
								lib/weather/structs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								lib/weather/structs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| package weather | ||||
|  | ||||
| type ( | ||||
| 	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"` | ||||
|  | ||||
| 		Weather []struct { | ||||
| 			Main        string `json:"main"` | ||||
| 			Description string `json:"description"` | ||||
| 			Icon        string `json:"icon"` | ||||
| 		} `json:"weather"` | ||||
| 	} | ||||
|  | ||||
| 	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 | ||||
| } | ||||
							
								
								
									
										84
									
								
								lib/weather/weather.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								lib/weather/weather.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| package weather | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"git.kill0.net/chill9/beepboop/lib" | ||||
|  | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| type ( | ||||
| 	WeatherClient struct { | ||||
| 		token string | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	OpenWeatherMapURI = "https://api.openweathermap.org" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	EndpointWeather        = lib.BuildURI(OpenWeatherMapURI, "/data/2.5/weather") | ||||
| 	ErrUnmarshal           = errors.New("unmarshaling JSON failed") | ||||
| 	ErrReadingResponse     = errors.New("reading HTTP response failed") | ||||
| 	ErrRequestFailed       = errors.New("HTTP request failed") | ||||
| 	ErrCreateRequestFailed = errors.New("failed to create new HTTP request") | ||||
| ) | ||||
|  | ||||
| func NewClient(token string) *WeatherClient { | ||||
| 	return &WeatherClient{token} | ||||
| } | ||||
|  | ||||
| func (c *WeatherClient) Get(loc string) (w Weather, err error) { | ||||
| 	var werr WeatherError | ||||
|  | ||||
| 	req, err := http.NewRequest("GET", EndpointWeather, nil) | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("%s: %s", ErrCreateRequestFailed, err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	q := req.URL.Query() | ||||
| 	q.Add("q", loc) | ||||
| 	q.Add("appid", c.token) | ||||
| 	req.URL.RawQuery = q.Encode() | ||||
|  | ||||
| 	resp, err := http.DefaultClient.Do(req) | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("%s: %s", ErrRequestFailed, err) | ||||
| 		return | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
|  | ||||
| 	body, err := io.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		err = fmt.Errorf("%s: %s", ErrReadingResponse, err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if resp.StatusCode != http.StatusOK { | ||||
| 		err = json.Unmarshal(body, &werr) | ||||
| 		if err != nil { | ||||
| 			log.Debugf("%s", body) | ||||
| 			err = fmt.Errorf("%s: %s", ErrUnmarshal, err) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		err = fmt.Errorf("error: (%s) %s", resp.Status, werr.Message) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	err = json.Unmarshal(body, &w) | ||||
| 	if err != nil { | ||||
| 		log.Debugf("%s", body) | ||||
| 		err = fmt.Errorf("%s: %s", ErrUnmarshal, err) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	return | ||||
| } | ||||
		Reference in New Issue
	
	Block a user