Documentation
¶
Index ¶
- Constants
- Variables
- func AudioProcessBuffer(sp AudioSpeaker, inBuf io.ReadCloser, st <-chan AudioState) error
- func AudioProcessBytes(sp AudioSpeaker, b []byte, st <-chan AudioState) error
- func AudioProcessCommand(sp AudioSpeaker, cmd *exec.Cmd, st <-chan AudioState) error
- func CacheScope(key string, getScope func() (int64, error)) (int64, error)
- func Clean(s string) string
- func EventAwait[T Event[T]](timeout time.Duration, check func(T) bool) T
- func EventLoop()
- func FlagPerson(person *int64, f *Flags)
- func FlagPlace(place *int64, f *Flags)
- func Format(cmd CommandStatic, prefix string) string
- func FormatQuote(cmd CommandStatic, prefix string, msng Messenger) string
- func IsValidURL(rawURL string) bool
- func OnlyOneBitSet(n int) bool
- func Rand() *mrand.Rand
- func Split(text string, lenCnt func(string) int, lenLim int) []string
- func TypeFlag(p *CommandType, value CommandType, f *Flags)
- type AudioPlayer
- func (p *AudioPlayer[T]) Append(item T)
- func (p *AudioPlayer[T]) Current() AudioState
- func (p *AudioPlayer[T]) HandlePlay(handler func(T, <-chan AudioState) error)
- func (p *AudioPlayer[T]) LoopAll()
- func (p *AudioPlayer[T]) LoopCurrent()
- func (p *AudioPlayer[T]) Next() (T, bool)
- func (p *AudioPlayer[T]) Pause()
- func (p *AudioPlayer[T]) Play()
- func (p *AudioPlayer[T]) Queue() []T
- func (p *AudioPlayer[T]) Seek()
- func (p *AudioPlayer[T]) Shuffle()
- func (p *AudioPlayer[T]) Skip()
- func (p *AudioPlayer[T]) Start()
- func (p *AudioPlayer[T]) Stop()
- type AudioSpeaker
- type AudioState
- type Command
- type CommandCategory
- type CommandRuntime
- type CommandStatic
- type CommandType
- type CommandsStatic
- type Event
- type Flags
- type FrontendType
- type Frontender
- type Frontenders
- type Hooks
- type Message
- func (m *Message) CommandParse() (*Message, error)
- func (m *Message) CommandRun() (*Message, error)
- func (m *Message) Fields() []string
- func (m *Message) FieldsSpace() []string
- func (m *Message) Hooks() *Hooks[*Message]
- func (m *Message) Prefixes() ([]Prefix, bool, error)
- func (m *Message) RawArgs(n int) string
- func (m *Message) Usage() any
- func (m *Message) Write(msg any, urr error) (*Message, error)
- type Messenger
- type Personifier
- type Place
- type Placer
- type Prefix
- type RedeemClaim
- type SQLDB
- func (db *SQLDB) Begin() (*Tx, error)
- func (db *SQLDB) Close() error
- func (db *SQLDB) Init(schema string) error
- func (db *SQLDB) PersonGet(col string, person, place int64) Val
- func (db *SQLDB) PersonSet(col string, person, place int64, val any) error
- func (db *SQLDB) PlaceGet(col string, place int64) Val
- func (db *SQLDB) PlaceSet(col string, place int64, val any) error
- func (db *SQLDB) PrefixList(place int64) ([]Prefix, error)
- func (_ *SQLDB) ScopeAdd(tx *sql.Tx, frontendID string, frontend int) (int64, error)
- func (db *SQLDB) ScopeFrontend(scope int64) (int64, error)
- func (db *SQLDB) ScopeID(scope int64) (string, error)
- type States
- type StreamOffline
- type StreamOnline
- type Tx
- func (tx *Tx) Commit() error
- func (tx *Tx) PersonEnsure(person, place int64) error
- func (tx *Tx) PersonGet(col string, person, place int64) Val
- func (tx *Tx) PersonSet(col string, person, place int64, val any) error
- func (tx *Tx) PlaceEnsure(place int64) error
- func (tx *Tx) PlaceGet(col string, place int64) Val
- func (tx *Tx) PlaceSet(col string, place int64, val any) error
- func (tx *Tx) Rollback() error
- type Urr
- type Val
- func (v Val) Bool() (bool, error)
- func (v Val) Duration() (time.Duration, error)
- func (v Val) Int() (int, error)
- func (v Val) Int64() (int64, error)
- func (v Val) Str() (string, error)
- func (v Val) StrNil() (string, Urr, error)
- func (v Val) Time() (time.Time, error)
- func (v Val) UUIDNil() (uuid.UUID, Urr, error)
Constants ¶
const ( CommandCategoryGames = "Games" CommandCategoryModerators = "Moderators" CommandCategoryOther = "Other" CommandCategoryServices = "Services" )
Variables ¶
var ( UrrMissingArgs = UrrNew("not enough arguments provided") UrrSilence = UrrNew("if this error is returned don't send any message") )
var ( EventMessage = make(chan *Message) EventMessageHooks = HooksNew[*Message](20) EventRedeemClaim = make(chan *RedeemClaim) EventRedeemClaimHooks = HooksNew[*RedeemClaim](5) EventStreamOnline = make(chan *StreamOnline) EventStreamOnlineHooks = HooksNew[*StreamOnline](5) EventStreamOffline = make(chan *StreamOffline) EventStreamOfflineHooks = HooksNew[*StreamOffline](5) )
var ( VirtualHost string Port string TikTokSessionID string YouTubeKey string OpenAIKey string MinGodInterval time.Duration Gin = gin.Default() )
var AliasesAdd = []string{
"add",
"new",
"create",
"+",
}
var AliasesDelete = []string{
"delete",
"del",
"remove",
"rm",
"-",
}
var AliasesEdit = []string{
"edit",
"modify",
"change",
}
var AliasesList = []string{
"list",
"ls",
}
var AliasesOff = []string{
"off",
"disable",
"false",
}
var AliasesOn = []string{
"on",
"enable",
"true",
}
var AliasesSearch = []string{
"search",
"find",
"lookup",
}
var AliasesSet = []string{
"set",
"=",
}
var AliasesShow = []string{
"show",
"view",
"get",
"status",
"state",
"current",
"?",
}
var Prefixes = prefixes{}
var RDB *redis.Client
var Teleports = gosafe.Map[int64, Place]{}
Teleports holds the bot-admin teleports defined by the teleport command. At most, only a handful of people are ever expected to be bot-admins, so using a map should suffice.
var UrrUnknownFrontend = UrrNew("Can't recognize the provided frontend.")
var UrrValNil = UrrNew("Value doesn't exist.")
Functions ¶
func AudioProcessBuffer ¶
func AudioProcessBuffer(sp AudioSpeaker, inBuf io.ReadCloser, st <-chan AudioState) error
AudioProcessBuffer will pipe audio coming from a buffer into ffmpeg and transform into audio that the speaker can transmit.
func AudioProcessBytes ¶
func AudioProcessBytes(sp AudioSpeaker, b []byte, st <-chan AudioState) error
AudioProcessBytes works exactly like AudioProcessBuffer except it accepts a slice of bytes instead of a buffer. Provided just for convenience.
func AudioProcessCommand ¶
func AudioProcessCommand(sp AudioSpeaker, cmd *exec.Cmd, st <-chan AudioState) error
AudioProcessCommand works exactly like AudioProcessBuffer except it accepts a command instead of a buffer. Provided just for convenience.
func CacheScope ¶
CacheScope returns the scope by looking it up in the cache, if it doesn't exist, then it fetches it from the DB using getScope and then caches it. The key should be globally unique.
func Clean ¶
Clean returns a string with every character except the ones in the a-z, A-Z and 0-9 ranges stripped from the passed string. Assumes ASCII.
func EventAwait ¶
EventAwait monitors incoming events until check is true or until timeout. If nothing is matched, then the returned object will be the default value of the type.
func EventLoop ¶
func EventLoop()
EventLoop starts an infinite loop which handles all incoming events. It's possible to have multiple instances running in separate goroutines (in order for the bot not to lag when handling an event that takes longer than virtually instantly); Golang guarantees that only one of the receivers will receive the channel data.
func FlagPerson ¶
func Format ¶
func Format(cmd CommandStatic, prefix string) string
Format will return a string representation of the given command in a format that can be shown to a user. Generally used in help messages to point the user to a specific command in order to avoid hard-coding it. Returns the command in the following format:
<prefix><command> [sub-command...] <usage-args>
For example: !command delete <command>
func FormatQuote ¶
func FormatQuote(cmd CommandStatic, prefix string, msng Messenger) string
FormatQuote works much like Format but also quotes the resulting string using Messenger.QuoteCommand.
func IsValidURL ¶
IsValidURL returns true if the provided string is a valid URL with a http or https scheme and a host.
func OnlyOneBitSet ¶
func Split ¶
Split splits a message into sub-messages. Tries to not split words unless it absolutely has to, in which case it splits based on grapheme clusters.
func TypeFlag ¶
func TypeFlag(p *CommandType, value CommandType, f *Flags)
Types ¶
type AudioPlayer ¶
type AudioPlayer[T any] struct { // contains filtered or unexported fields }
func (*AudioPlayer[T]) Append ¶
func (p *AudioPlayer[T]) Append(item T)
func (*AudioPlayer[T]) Current ¶
func (p *AudioPlayer[T]) Current() AudioState
func (*AudioPlayer[T]) HandlePlay ¶
func (p *AudioPlayer[T]) HandlePlay(handler func(T, <-chan AudioState) error)
func (*AudioPlayer[T]) LoopAll ¶
func (p *AudioPlayer[T]) LoopAll()
func (*AudioPlayer[T]) LoopCurrent ¶
func (p *AudioPlayer[T]) LoopCurrent()
func (*AudioPlayer[T]) Next ¶
func (p *AudioPlayer[T]) Next() (T, bool)
func (*AudioPlayer[T]) Pause ¶
func (p *AudioPlayer[T]) Pause()
func (*AudioPlayer[T]) Play ¶
func (p *AudioPlayer[T]) Play()
func (*AudioPlayer[T]) Queue ¶
func (p *AudioPlayer[T]) Queue() []T
func (*AudioPlayer[T]) Seek ¶
func (p *AudioPlayer[T]) Seek()
func (*AudioPlayer[T]) Shuffle ¶
func (p *AudioPlayer[T]) Shuffle()
func (*AudioPlayer[T]) Skip ¶
func (p *AudioPlayer[T]) Skip()
func (*AudioPlayer[T]) Start ¶
func (p *AudioPlayer[T]) Start()
func (*AudioPlayer[T]) Stop ¶
func (p *AudioPlayer[T]) Stop()
type AudioSpeaker ¶
type AudioSpeaker interface { // Enabled returns true if the frontend supports voice chat that the bot can // connect to. Enabled() bool // FrameRate returns the audio's expected frame rate. Is passed to ffmpeg // to convert the audio stream to the correct format before sending. FrameRate() int // Channels returns the audio's expected number of channels. Is passed to // ffmpeg to convert the audio stream to the correct format before sending. Channels() int // Join the message author's voice channel, if they are not connected to // any then returns an error. If in a specific frontend only one voice // channel ever exists, then the user doesn't have to be connected to it // for the bot to join (for example, a discord server with only one voice // channel would not apply here as other ones *could* be created at any // point). Join() error // Leave disconnects the bot from the connected to voice channel, if not // connected to a voice channel then returns error. Leave() error // Say sends audio. Must have connected to a voice channel first, otherwise // returns an error. Will only ever have to handle the following states: // // - AudioPlay, the default state, keep sending audio // - AudioPause, pause the audio and wait, if AudioPlay is received resume // - AudioStop, stop sending audio and return nil Say(buf io.Reader, st <-chan AudioState) error // AuthorDeafened returns true if the author that originally made the bot // join the voice channel is currently deafened. AuthorDeafened() (bool, error) // AuthorConnected returns true if the author that originally made the bot // join the voice channel is currently connected to that same voice channel. AuthorConnected() (bool, error) }
type AudioState ¶
type AudioState int
const ( AudioPlay AudioState = iota AudioPause AudioStop AudioSeek AudioLoopAll AudioLoopCurrent AudioSkip AudioShuffle )
type Command ¶
type Command struct { CommandStatic CommandRuntime }
type CommandCategory ¶
type CommandCategory string
type CommandRuntime ¶
type CommandRuntime struct { // The "path" taken to invoke the command, i.e., which names were used. // Includes all the sub-commands e.g. ["prefix", "add"] in order to be able // to display accurate help messages. Path []string // The arguments passed, includes everything that's not part of the // command's name. Args []string // The prefix used when the command was called. Prefix string }
CommandRuntime holds a command's runtime information.
type CommandStatic ¶
type CommandStatic interface { // Type returns the command's type. Type() CommandType // Permitted will perform checks required for a command to be executed. // Returns true if the command is allowed to be executed. Usually used to // check a user's permissions or to restrict a command to specific // frontends. Permitted(m *Message) bool // Names return a list of all the aliases a command has. The first item in // the list is considered the main name and so should be the simplest and // most intuitive one for the average person. For example, if it's a deleted // subcommand, the first alias should be "delete" instead of "del" or "rm." Names() []string // Description will return a short description of what the command does. Description() string // UsageArgs will return the usage arguments. Should follow this format: // - <required> // - [optional] // - (literal-string) or (many | literals) UsageArgs() string // Category returns the general category the command belongs to. Mainly // to make displaying all the commands easier and less overwhelming (as // they are split up instead of having them all in a giant list). Category() CommandCategory // Examples returns a list of strings with example usages of the command. // Only the arguments passed should be included, not the prefix and the // chain of command names. Examples() []string // Parent returns a command's parent, returns nil if there is no parent. Parent() CommandStatic // Children returns the command's sub-commands, returns nil if there are no // sub-commands. Children() CommandsStatic // Init is executed during bot startup. Should be used to set things up // necessary for the command, for example, DB schemas. Init() error // Run is the function that is called to run the command. Run(m *Message) (resp any, urr Urr, err error) }
CommandStatic is the interface used to implement commands.
type CommandType ¶
type CommandType int
const ( // Normal represents a simplified command which might not give full control // over something, but it has a very easy to use API. Normal CommandType = 1 << iota // Advanced represents the full command and usually consists of many // subcommands which makes it less intuitive for the average person. Advanced // Admin represents a bot-admin-only command used to perform actions like // setting an arbitrary person's info, etc. Admin All = Normal | Advanced | Admin )
The command types.
type CommandsStatic ¶
type CommandsStatic []CommandStatic
var Commands CommandsStatic
func (CommandsStatic) Match ¶
func (cmds CommandsStatic) Match(t CommandType, m *Message, args []string) (CommandStatic, int, error)
Match will return the corresponding command based on the list of arguments. The arguments don't have to match a command exactly, for example,
args = [prefix add abc]
In this case, the prefix's subcommand "add" will be matched and returned. Alongside it, the index of the last valid command will be returned (in this case, the index of "add," which is 1).
func (CommandsStatic) Recurse ¶
func (cmds CommandsStatic) Recurse(exec func(CommandStatic))
Recurse will recursively go through all the commands and execute the exec function on them.
func (CommandsStatic) Usage ¶
func (cmds CommandsStatic) Usage() string
Usage returns the names of the children in a format that can be used in the UsageArgs function.
func (CommandsStatic) UsageOptional ¶
func (cmds CommandsStatic) UsageOptional() string
UsageOptional returns the names of the children in a format that can be used in the UsageArgs function and indicates that the sub-commands are optional.
type Event ¶
type Event[T any] interface { *Message | *RedeemClaim | *StreamOnline | *StreamOffline Hooks() *Hooks[T] }
type FrontendType ¶
type FrontendType int
type Frontender ¶
type Frontender interface { // Type returns the frontend type ID. Type() FrontendType // Name returns frontend's name formatted in all lowercase. Name() string // Init is responsible for starting up any frontend-specific services and // connecting to frontend. When it receives the stop signal, then it should // disconnect from everything. Init(wgInit, wgStop *sync.WaitGroup, stop chan struct{}) // CreateMessage returns a Message object based on the given arguments. // Used to send messages that are not direct replies, e.g. reminders. CreateMessage(person, place int64, msgID string) (*Message, error) // Usage returns the passed usage formatted appropriately for the frontend. Usage(usage string) any // PlaceExact returns the exact scope of the specified ID. PlaceExact(id string) (place int64, err error) // PlaceLogical returns the logical scope of the specified ID. PlaceLogical(id string) (place int64, err error) }
type Frontenders ¶
type Frontenders []Frontender
var Frontends Frontenders
func (Frontenders) CreateMessage ¶
func (fs Frontenders) CreateMessage(person, place int64, msgID string) (*Message, error)
CreateMessage returns a Message object based on the given arguments. It detects what the frontend is based on the place. Used to send messages that are not direct replies, e.g. reminders.
func (Frontenders) Match ¶
func (fs Frontenders) Match(fname string) (Frontender, Urr)
Match returns the Frontender corresponding to lowercase fname. Returns UrrUnknownFrontend if nothing is matched.
type Hooks ¶
type Hooks[T any] struct { // contains filtered or unexported fields }
Hooks are a list of functions that are applied one-by-one to incoming events. All operations are thread safe.
func HooksNew ¶
HooksNew generates a new Hooks object. Spawns n number of receiver functions in their own goroutines.
func (*Hooks[T]) Delete ¶
Delete will delete the hook with the given id. If the hook doesn't exist, then nothing happens.
type Message ¶
type Message struct { ID string Raw string Frontend Frontender Author Personifier Here Placer Client Messenger Speaker AudioSpeaker Command *Command }
func (*Message) CommandParse ¶
func (*Message) CommandRun ¶
func (*Message) FieldsSpace ¶
FieldsSpace splits text into fields that include all trailing whitespace. For example, "example of text" will be split into ["example ", "of ", "text"]
func (*Message) Prefixes ¶
Prefixes returns the logical here's prefixes, and also whether they were taken from the database (if not, then that means the default ones were used).
type Messenger ¶
type Messenger interface { Parse() (*Message, error) // PlaceID returns the ID of the passed string. The returned ID must be // valid. Generally used for verifying an ID's validity and extracting IDs // from mentions. PlaceID(s string) (id string, err error) // PersonID returns the ID of the passed string. The returned ID must be // valid. Generally used for verifying an ID's validity and extracting IDs // from mentions. PersonID(s, placeID string) (id string, err error) // Person returns the target's scope. // If it doesn't exist, it will create it and add it to the database. Person(id string) (person int64, err error) // Send sends a message to the appropriate scope, resp could be nil // depending on the frontend. Send(msg any, urr Urr) (resp *Message, err error) // Ping works the same as Send except the user is also pinged. Ping(msg any, urr Urr) (resp *Message, err error) // Write either calls Send or Ping depending on the frontend. This is what // should be used in most cases. Write(msg any, urr Urr) (resp *Message, err error) // Natural will try to emulate a response as if an actual human had written // it. Often the bot uses markers to distinguish its responses (for example, // on Twitch it replies with the following format: @person -> <resp>), which // are not natural looking. To add to the effect, randomness may be used to // only sometimes mention the person. Natural(msg any, urr Urr) (resp *Message, err error) // QuoteCommand returns the passed cmd quoted appropriately. // If frontend-specific formatting doesn't exist, then use single quotes. QuoteCommand(cmd string) string }
Messenger is the abstraction layer for message events.
type Personifier ¶
type Personifier interface { // ID returns the author's ID, this should be a unique, static, identifier // in that frontend. ID() (string, error) // Name returns the author's username. Name() (string, error) // DisplayName returns the author's display name. // If only usernames exist for that frontend, then return the username. DisplayName() (string, error) // Mention returns a string that mentions the author. // This should ideally ping them in some way. Mention() (string, error) // BotAdmin returns true if the author is a bot admin, otherwise returns // false. BotAdmin() (bool, error) // Admin checks if the author is considered an admin. // Should return true only if the author has basically every permission. Admin() (bool, error) // Moderator checks if the author is considered a moderator. General rule of // thumb is that if the author can ban people, then they are mods. Moderator() (bool, error) // Subscriber returns true if the author is considered a subscriber. General // rule of thumb is that if they are paying money in some way, then they are // a subscriber. If no such thing exists for the specific frontend, then it // should always return false. Subscriber() (bool, error) // Scope return's the author's scope. // If it doesn't exist, it will create it and add it to the database. Scope() (author int64, err error) }
Personifier is the interface used to abstract a frontend's message author.
type Placer ¶
type Placer interface { // IDExact returns the exact ID, this should be a unique, static, // identifier in that frontend. IDExact() string // IDLogical returns the logical ID, this should be a unique, static, // identifier for the frontend. IDLogical() string // Name return's the channel's name. Name() string // ScopeExact returns the here's exact scope. // Returns the exact scope in Teleports if the author is present there. // See the interface's doc comment for more information on exact scopes. ScopeExact() (place int64, err error) // ScopeLogical returns the here's logical scope. // Returns the logical scope in Teleports if the author is present there. // See the interface's doc comment for more information on logical scopes. ScopeLogical() (place int64, err error) }
Placer is the interface used to abstract the place where an event came from, e.g. channel, server, etc.
Two type's of scopes exist for places, the exact and the logical. The logical is the area where things are generally expected to work. For example, if a user adds a custom command in a discord server, they would probably expect it to work in the entire server and not just in the specific channel that they added it in. If, on the other hand, someone adds a custom command in a discord DM, then no guild exists and thus the channel's scope would have to be used. On the other hand, the exact scope is, as its name suggests, the scope of the exact place the message came from and does not account for context, so using the previous discord server example, it would be the channel's scope where the message came from instead of the server's.
type Prefix ¶
type Prefix struct { Type CommandType Prefix string }
type RedeemClaim ¶
type RedeemClaim struct { ID string Input string When time.Time Author Personifier Here Placer Frontend Frontender }
func (*RedeemClaim) Hooks ¶
func (rc *RedeemClaim) Hooks() *Hooks[*RedeemClaim]
type SQLDB ¶
var DB *SQLDB
func (*SQLDB) PrefixList ¶
PrefixList returns the list of all prefixes for a specific scope.
func (*SQLDB) ScopeFrontend ¶
ScopeFrontend returns the given scope's frontend id
type States ¶
States provides some helpful functionality for dealing with the OAuth state parameter.
type StreamOffline ¶
type StreamOffline struct { When time.Time Here Placer Frontend Frontender }
func (*StreamOffline) Hooks ¶
func (son *StreamOffline) Hooks() *Hooks[*StreamOffline]
type StreamOnline ¶
type StreamOnline struct { When time.Time Here Placer Frontend Frontender }
func (*StreamOnline) Hooks ¶
func (son *StreamOnline) Hooks() *Hooks[*StreamOnline]
type Tx ¶
func (*Tx) PersonEnsure ¶
PersonEnsure checks if the person info for place exists, if not, generates it.
func (*Tx) PersonGet ¶
PersonGet returns the value of col in the table for the specified person in the specified place.
func (*Tx) PersonSet ¶
PersonSet sets the value of col in the table for the specified person in the specified place.
func (*Tx) PlaceEnsure ¶
PlaceEnsure checks if the place info exists, if not, generates it.
type Urr ¶
type Urr error
Urr represents a user-induced error (as opposed to an unexpected internal error.) This means that if such an error is returned, the user should, in some way, be informed of what their mistake was and not just receive a message along the lines of "something went wrong"