Documentation ¶
Overview ¶
Package sdbot implements the Pokemon Showdown protocol and provides a library to interact with users.
Usage ¶
The Bot type represents the bot and its state. To create a Bot, call the NewBot function. This currently assumes that your configuration toml file is both called config.toml and is located in the same directory as your bot script. Expect this to change in later versions.
To connect to the server, call the bot's Connect method.
To register plugins, write your plugins under a package and import them. Register them by calling the bot's RegisterPlugin and RegisterTimedPlugin methods. It is recommended to register your plugins before connecting to the server.
Concurrency ¶
Each bot will spawn multiple goroutines for both reading and writing to the socket, as well as for plugin loops listening for matches from the server messages. The bot internal state is designed for concurrent access, so two goroutines looking to read the bot's state, such as the userlist, will get the same result.
A bot will spawn a separate goroutine to run every Plugin event. For this reason, applications are responsible for ensuring that the plugin EventHandlers are safe for concurrent use. For this reason, Bot exports a Synchronize method that takes a pointer to a function and an identifying string that will run all functions corresponding to that identifying string on the same mutex.
Consider you want a plugin that reads and writes to a map defined in its event handler. Maps cannot be read and written to concurrently, so you need to ensure that another plugin event must wait until the current one is done.
func (eh *PluginEventHandler) HandleEvent(m *sdbot.Message, args []string) { var readVal interface{} rk := someKey() wk := anotherKey() rw := func() interface{} { eh.Map[wk] = someVal() readVal = eh.Map[rk] return nil } m.Bot.Synchronize("maprw", &rw) }
Index ¶
- Constants
- Variables
- func AddLogger(lo Logger)
- func CheckErr(err error)
- func CheckErrAll(err error)
- func Debug(s string)
- func Debugf(format string, a ...interface{})
- func Error(err error)
- func ErrorAll(err error)
- func Errorf(format string, a ...interface{})
- func Fatal(s string)
- func Fatalf(format string, a ...interface{})
- func Haste(buf io.Reader, bodyType string) (string, error)
- func Info(s string)
- func Infof(format string, a ...interface{})
- func Inspect(i interface{})
- func RemoveLogger(lo Logger) bool
- func Rename(old string, s string, r *Room, b *Bot, auth string)
- func Sanitize(s string) string
- func SanitizeRoomid(s string) string
- func Warn(s string)
- func Warnf(format string, a ...interface{})
- type AnyLogger
- type BattleInfo
- type Bot
- func (b *Bot) Connect()
- func (b *Bot) JoinRoom(room *Room)
- func (b *Bot) LeaveRoom(room *Room)
- func (b *Bot) RegisterPlugin(p *Plugin, name string) error
- func (b *Bot) RegisterPlugins(plugins map[string]*Plugin) error
- func (b *Bot) RegisterTimedPlugin(tp *TimedPlugin, name string) error
- func (b *Bot) Send(s string)
- func (b *Bot) StartTimedPlugins()
- func (b *Bot) StopTimedPlugins()
- func (b *Bot) Synchronize(name string, lambda *func() interface{}) interface{}
- func (b *Bot) UnregisterPlugin(p *Plugin) bool
- func (b *Bot) UnregisterPlugins()
- func (b *Bot) UnregisterTimedPlugin(tp *TimedPlugin) bool
- type Config
- type Connection
- type DefaultEventHandler
- type DefaultLogger
- type DefaultTimedEventHandler
- type EventHandler
- type HasteKey
- type Logger
- type LoggerList
- type LoggerProvider
- type Message
- type Plugin
- type PrettyLogger
- type RecentBattles
- type Room
- type Target
- type TimedEventHandler
- type TimedPlugin
- type User
Constants ¶
const ( Administrator = `~` Leader = `&` RoomOwner = `#` Moderator = `@` Driver = `%` TheImmortal = `>` Battler = `★` Voiced = `+` Unvoiced = ` ` Muted = `?` Locked = `‽` )
String values of all the auth levels.
Variables ¶
var ErrPluginAlreadyRegistered = errors.New("sdbot: plugin was already registered")
ErrPluginAlreadyRegistered is returned whenever the same plugin is attempted to be registered twice.
var ErrPluginNameAlreadyRegistered = errors.New("sdbot: plugin name was already in use (register under another name)")
ErrPluginNameAlreadyRegistered is returned whenever a plugin with a particular name is attempted to be registered, but the name has previously been registered.
var ErrUnexpectedMessageType = errors.New("sdbot: unexpected message type from the websocket")
ErrUnexpectedMessageType is returned when we receive a message from the websocket that isn't a websocket.TextMessage or a normal closure.
LoginTime contains the unix login times as values to each particular room the bot has joined. This allows us to ignore messages that occurred before the bot has logged in.
Functions ¶
func CheckErr ¶
func CheckErr(err error)
CheckErr checks if an error is nil, and if it is not, logs the error to default os.Stderr logger. TODO Perhaps a nice stack trace here as well.
func CheckErrAll ¶
func CheckErrAll(err error)
CheckErrAll checks if an error is nil, and if it is not, logs the error to all the loggers in the LoggerList. TODO Perhaps a nice stack trace here as well.
func Debugf ¶
func Debugf(format string, a ...interface{})
Debugf logs debug messages with formatting to the default os.Stderr logger.
func Errorf ¶
func Errorf(format string, a ...interface{})
Errorf logs errors with formatting to the default os.Stderr logger.
func Fatalf ¶
func Fatalf(format string, a ...interface{})
Fatalf logs fatal messages with formatting to the default os.Stderr logger.
func Haste ¶
Haste uploads a file to Hastebin and returns the response URL. TODO Cute, but perhaps this should be its own package.
func Infof ¶
func Infof(format string, a ...interface{})
Infof logs informatic messages with formatting to the default os.Stderr logger.
func Inspect ¶
func Inspect(i interface{})
Inspect logs a debug message to the default os.Stderr logger, taking any interface and inpecting its contents.
func RemoveLogger ¶
RemoveLogger removes a logger from the LoggerLit Loggers. Returns true if the logger was successfully removed.
func Sanitize ¶
Sanitize returns a new string with non-alphanumeric characters removed. This is particularly useful when it comes to identifying unique usernames. Keep in mind that Pokemon Showdown usernames are _not_ case sensitive, so a downcased and sanitized username is a unique identifier.
func SanitizeRoomid ¶
SanitizeRoomid returns a new string with non-alphanumeric characters removed, excepting hyphens. This is used for returning the proper roomids used on PS! - groupchats are formatted as groupchat-CREATOR-NAME, and without these hyphens, we are given an incorrect identifier.
Types ¶
type AnyLogger ¶
AnyLogger represents a container for a logger. It knows WHERE to log the messages and uses a mutex so as to not log out of order. What it does not know is HOW to format its messages. See DefaultLogger and PrettyLogger to see how this works.
type BattleInfo ¶
type BattleInfo struct { FirstPlayer string `json:"p1"` SecondPlayer string `json:"p2"` MinElo string `json:"minElo"` }
BattleInfo holds the roomlist JSON unmarshalling for each battle.
type Bot ¶
type Bot struct { Config *Config Connection *Connection UserList map[string]*User RoomList map[string]*Room Rooms []string Nick string Plugins []*Plugin TimedPlugins []*TimedPlugin PluginChatChannels map[string]*chan *Message PluginPrivateChannels map[string]*chan *Message BattleFormats []string RecentBattles chan *RecentBattles // contains filtered or unexported fields }
Bot represents the entrypoint to all the necessary behaviour of the bot. The bot runs its handlers in separate goroutines, so an API is provided to allow for thread-safe and concurrent access to the bot. See the Synchronize method for how this works.
func NewBot ¶
NewBot creates a new instance of the Bot struct. In doing so it creates a new Connection as well as adds a PrettyLogger to the Loggers that logs to os.Stderr. It takes a path to the configuration TOML file.
func (*Bot) RegisterPlugin ¶
RegisterPlugin registers a plugin under a name and starts listening on its event handler.
func (*Bot) RegisterPlugins ¶
RegisterPlugins registers a slice of plugins in one call. The map should be formatted with pairs of "plugin name"=>*Plugin.
func (*Bot) RegisterTimedPlugin ¶
func (b *Bot) RegisterTimedPlugin(tp *TimedPlugin, name string) error
RegisterTimedPlugin registers a timed plugin under the provided name. Timed plugins are not started until the bot is logged in.
func (*Bot) StartTimedPlugins ¶
func (b *Bot) StartTimedPlugins()
StartTimedPlugins starts all registered TimedPlugins.
func (*Bot) StopTimedPlugins ¶
func (b *Bot) StopTimedPlugins()
StopTimedPlugins stops all registered TimedPlugins.
func (*Bot) Synchronize ¶
Synchronize provides an API to keep your code thread-safe and concurrent. For example, if a plugin is going to write to a file, it would be a bad idea to have multiple threads with different state to try to access the file at the same time. So you should run such commands under Bot.Synchronize. The name is an arbitrary name you can choose for your mutex. The lambda is Then run in the mutex defined by the name. Choose unique names!
Example:
var doUnsafeAction = func() interface{} { someActionThatMustBePerformedSequentially() return nil }
bot.Synchronize("uniqueIdentifierForUnsafeAction", &doUnsafeAction)
func (*Bot) UnregisterPlugin ¶
UnregisterPlugin unregisters a plugin. Returns true if the plugin was successfully unregistered.
func (*Bot) UnregisterPlugins ¶
func (b *Bot) UnregisterPlugins()
UnregisterPlugins unregisters all plugins.
func (*Bot) UnregisterTimedPlugin ¶
func (b *Bot) UnregisterTimedPlugin(tp *TimedPlugin) bool
UnregisterTimedPlugin unregisters a timed plugin. Returns true if the plugin was successfully unregistered.
type Config ¶
type Config struct { Server string Port string Nick string Password string MessagesPerSecond float64 Rooms []string Avatar int PluginPrefixes []string PluginSuffixes []string PluginPrefix *regexp.Regexp PluginSuffix *regexp.Regexp CaseInsensitive bool IgnorePrivateMessages bool IgnoreChatMessages bool }
Config holds the configuration information read from the config.toml file.
type Connection ¶
type Connection struct { Bot *Bot Connected bool LoginTime map[string]int // contains filtered or unexported fields }
Connection represents the connection to the websocket.
func NewConnection ¶
func NewConnection(b *Bot) *Connection
NewConnection creates a new connection for a bot.
func (*Connection) QueueMessage ¶
func (c *Connection) QueueMessage(msg string)
QueueMessage adds a message to the outgoing queue.
type DefaultEventHandler ¶
type DefaultEventHandler struct {
Plugin *Plugin
}
DefaultEventHandler is the default event handler that you can make use of if you do not want to encapsulate your plugin with any custom behaviour. Refer to the example plugins to see how you can make use of this.
type DefaultLogger ¶
type DefaultLogger struct {
AnyLogger
}
DefaultLogger is the default logger type. Formats messages by doing nothing to them.
type DefaultTimedEventHandler ¶
type DefaultTimedEventHandler struct {
TimedPlugin *TimedPlugin
}
DefaultTimedEventHandler is the default event handler for a TimedPlugin. Refer to the example plugins to see how you can make use of this.
type EventHandler ¶
EventHandler defines the behaviour and action of any event on a Plugin. Use the DefaultEventHandler unless you want to add custom behaviour. For example, you could keep track of variables that are known globally to the plugin. (Every Plugin event goes through the same handler)
type HasteKey ¶
type HasteKey struct {
Key string
}
HasteKey holds the JSON unmarshalling of a Hastebin POST request.
type Logger ¶
type Logger interface {
// contains filtered or unexported methods
}
Logger is an interface that allows for creation your own loggers with custom formatting. See PrettyLogger for how this is done.
type LoggerList ¶
type LoggerList struct {
Loggers []Logger
}
LoggerList represents a list of Loggers with methods that allow you to log to every one of them as per each loggers' individual logging behaviour.
func NewLoggerList ¶
func NewLoggerList(loggers ...Logger) *LoggerList
NewLoggerList creates a new LoggerList from a slice of Loggers.
type LoggerProvider ¶
type LoggerProvider interface {
// contains filtered or unexported methods
}
LoggerProvider is an interface used to allow access to the struct fields of AnyLoggers. Somewhat of an golang hack.
type Message ¶
type Message struct { Bot *Bot Time time.Time Command string Params []string Timestamp int Room *Room User *User Auth string Target Target Message string Matches map[string]map[*regexp.Regexp][]string }
Message represents a message sent by a user to either a room the bot is currently in, or to the bot via private messages. A message also defines behaviour in its methods to reply to these messages.
func NewMessage ¶
NewMessage creates a new message and parses the message.
func (*Message) Match ¶
Match adds matches to the message and return true if there was no previous match and if there was indeed a match.
func (*Message) RawReply ¶
RawReply responds to a message without prepending anything to the message. Note that you should take care to not allow users to influence a raw reply message to do a client command. For this reason, prefer to use Reply unless you are responding with a static message. You may want to event freeze the string.
type Plugin ¶
type Plugin struct { Bot *Bot Name string Prefix *regexp.Regexp Suffix *regexp.Regexp Command string NumArgs int Cooldown time.Duration LastUsed time.Time EventHandler EventHandler // contains filtered or unexported fields }
Plugin is a command that triggers on some regexp and performs a task based on the message that triggered it as defined by its EventHandler. It can trigger on several different formats (consider prefix "." and command "cmd"). Note that cooldowns will not affect command syntax, but will ignore the commands hould it have been sent less than Cooldown time from the last instance.
NewPlugin: ".cmd" NewPluginWithArgs:
1 arg: ".cmd arg0" 2 args: ".cmd arg0, arg1" (space is optional) n args: ".cmd arg0,arg1,arg2,arg3, ...,arg n" (space is optional)
It is possible to define a Command with a regexp string. For example, a command of "y|n" will trigger on either y or n.
Note that if a command is given as something like s(ay|peak) then the parsed args will be pushed back in the slice, and args[0] will contain the string of either "ay" or "peak", depending on which triggered the message.
Each fired event is run in its own separate goroutine, so for anything that must be run sequentially (ie. cannot read and write to the same file at once) use Bot.Synchronize.
func NewPlugin ¶
NewPlugin (and its variants) define convenient ways to make new Plugin structs. You must add an event handler after creation. If you want to add custom prefixes or suffixes, you can do it with the Plugin.SetPrefix and Plugin.SetSuffix methods. Otherwise the bot will load prefixes and suffixes from your Config.
NewPlugin in particular creates a Plugin that will trigger on a command.
func NewPluginWithArgs ¶
NewPluginWithArgs creates a new Plugin that will trigger on a command, and will parse comma-separated arguments provided along with the command. It will not trigger unless an adequate amount of commas are present.
func NewPluginWithArgsAndCooldown ¶
NewPluginWithArgsAndCooldown creates a new Plugin with arguments and a cooldown as described by both NewPluginWithArgs and NewPluginWithCooldown.
func NewPluginWithCooldown ¶
NewPluginWithCooldown creates a new Plugin that will trigger on a command, but will not trigger if the last time it was used was not at least the provided time.Duration ago.
func NewPluginWithoutCommand ¶
func NewPluginWithoutCommand() *Plugin
NewPluginWithoutCommand creates a new Plugin that will trigger on every chat and private event.
func (*Plugin) SetEventHandler ¶
func (p *Plugin) SetEventHandler(eh EventHandler)
SetEventHandler sets the EventHandler of the Plugin. Allows you to use a custom EventHandler with any fields you want. The EventHandler of every Plugin MUST be set after the creation of a Plugin.
type PrettyLogger ¶
type PrettyLogger struct {
AnyLogger
}
PrettyLogger logs everything with pretty colours. Looks good with the Solarized terminal theme. Don't like how it looks? Make your own!
type RecentBattles ¶
type RecentBattles struct {
Battles map[string]BattleInfo
}
RecentBattles holds the roomlist JSON unmarshalling battle map.
type Room ¶
type Room struct { Name string Users []string // List of unique SANITIZED names of users in the room }
Room represents a room with its name and the users currently in it.
func FindRoomEnsured ¶
FindRoomEnsured finds a room if it exists, creates the room if it doesn't.
func (*Room) RemoveUser ¶
RemoveUser removes a user from the room.
type Target ¶
Target represents either a Room or a User. The distinction is in where the bot will send its message in response.
type TimedEventHandler ¶
type TimedEventHandler interface {
HandleEvent()
}
TimedEventHandler defines the behaviour and action of any event on a TimedPlugin. Use the DefaultTimedEventHandler unless you want to add custom behaviour.
type TimedPlugin ¶
type TimedPlugin struct { Bot *Bot Name string Ticker *time.Ticker Period time.Duration TimedEventHandler TimedEventHandler // contains filtered or unexported fields }
TimedPlugin structs will fire an event on a regular schedule defined by the time.Duration provided to the time.Ticker. Each event is run in its own goroutine, so every event will fire regardless of whether or not the last event ticked had completed. For anything that must be run sequentially (ie. cannot read and write to the same file at once) use Bot.Synchronize.
Because each event fires in its own goroutine, you should take care to not have each event take longer than the duration of the ticker, or else you will be spawning goroutines faster than you can finish them, which is a recipe for disaster.
func NewTimedPlugin ¶
func NewTimedPlugin(period time.Duration) *TimedPlugin
NewTimedPlugin creates a new TimedPlugin that fires events on its TimedEventHandler every given period of time.Duration.
func (*TimedPlugin) SetEventHandler ¶
func (tp *TimedPlugin) SetEventHandler(teh TimedEventHandler)
SetEventHandler sets the TimedEventHandler of the TimedPlugin. The TimedEventHandler of every TimedPlugin MUST be set after its creation.
type User ¶
User represents a user with their username and the auth levels in rooms that the bot knows about.
func FindUserEnsured ¶
FindUserEnsured finds a user if it exists, creates the user if it doesn't.
func (*User) HasAuth ¶
HasAuth checks if a user has AT LEAST a given authorization level in a given room.