cchat/cchat.go

182 lines
6.1 KiB
Go

package cchat
import (
"io"
"time"
"github.com/diamondburned/cchat/text"
)
// Service contains the bare minimum set of interface that a backend has to
// implement. Core can also implement Authenticator.
type Service interface {
Server
ServerList
}
// Configurator is what the backend can implement for an arbitrary configuration
// API.
type Configurator interface {
Configuration() (map[string]string, error)
SetConfiguration(map[string]string) error
}
// ErrInvalidConfigAtField is the structure for an error at a specific
// configuration field. Frontends can use this and highlight fields if the
// backends support it.
type ErrInvalidConfigAtField struct {
Key string
Err error
}
func (err *ErrInvalidConfigAtField) Error() string {
return "Error at " + err.Key + ": " + err.Err.Error()
}
func (err *ErrInvalidConfigAtField) Unwrap() error {
return err.Err
}
// Authenticator is what the backend can implement for authentication. A typical
// authentication frontend implementation would look like this:
//
// for {
// outputs := renderAuthForm(svc.AuthenticateForm())
// if err := svc.Authenticate(outputs); err != nil {
// log.Println("Error while authenticating:", err)
// continue // retry
// }
// break // success
// }
type Authenticator interface {
// AuthenticateForm should return a list of authentication entries for
// the frontend to render.
AuthenticateForm() []AuthenticateEntry
// Authenticate will be called with a list of values with indices
// correspond to the returned slice of AuthenticateEntry.
Authenticate([]string) error
}
// AuthenticateEntry represents a single authentication entry, usually an email
// or password prompt. Passwords or similar entries should have Secrets set to
// true, which should imply to frontends that the fields be masked.
type AuthenticateEntry struct {
Name string
Secret bool
}
// Commander is an optional interface that a backend could implement for command
// support. This is different from just intercepting the SendMessage() API, as
// this extends the entire service.
type Commander interface {
// RunCommand executes the given command, with the slice being already split
// arguments, similar to os.Args. The function could return an output
// stream, in which the frontend must display it live and close it on EOF.
RunCommand([]string) (io.ReadCloser, error)
}
// CommandCompleter is an optional interface that a backend could implement for
// completion support. This also depends on whether or not the frontend supports
// it.
type CommandCompleter interface {
// CompleteCommand is called with the line and current word, which the
// backend should return with a list of new words.
CompleteCommand(words []string, wordIndex int) []string
}
// Server is a single server-like entity that could translate to a guild, a
// channel, a chat-room, and such. A server must implement at least ServerList
// or ServerMessage, else the frontend must treat it as a no-op.
type Server interface {
// Name returns the server's name or the service's name.
Name() (string, error)
// Implement ServerList and/or ServerMessage.
}
// ServerIcon is an extra interface that Server could implement for an icon.
type ServerIcon interface {
IconURL() (string, error)
}
// ServerList is for servers that contain children servers. This is similar to
// guilds containing channels in Discord, or IRC servers containing channels.
//
// There isn't a similar LeaveServers() API like ServerMessage because all
// servers are expected to be listed. However, they could be hidden, such as
// collapsing a tree.
type ServerList interface {
// Servers should call SetServers() on the given ServersContainer to render
// all servers.
Servers(ServersContainer) error
}
// ServersContainer is a frontend implementation for a server view, with
// synchronous callbacks to render those events. The frontend is typically
// expected to reset the entire list, but it can do so with or without deleting
// everything and starting all over again.
type ServersContainer interface {
// SetServer is called by the backend service to request a reset of the
// server list. The frontend can choose to call Servers() on each of the
// given servers, or it can call that later. The backend should handle both
// cases.
SetServers([]Server)
}
// ServerMessage is for servers that contain messages. This is similar to
// Discord or IRC channels.
type ServerMessage interface {
// JoinServer should be called if Servers() returns nil, in which the
// backend should connect to the server and start calling methods in the
// container.
JoinServer(MessagesContainer) error
// LeaveServer indicates the backend to stop calling the controller over.
// This should be called before any other JoinServer() calls are made.
LeaveServer() error
// SendMessage is called by the frontend to send a message to this channel.
SendMessage(string) error
}
// Worth pointing out that frontend container interfaces will not have an error
// handling API, as frontends can do that themselves.
// MessagesContainer is a frontend implementation for a message view, with
// synchronous callbacks to render those events.
type MessagesContainer interface {
CreateMessage(MessageCreate)
UpdateMessage(MessageUpdate)
DeleteMessage(MessageDelete)
}
// MessageHeader implements the interface for any message event.
type MessageHeader interface {
ID() string
Time() time.Time
}
// MessageCreate is the interface for an incoming message.
type MessageCreate interface {
MessageHeader
Author() text.Rich
Content() text.Rich
}
// MessageUpdate is the interface for a message update (or edit) event. If the
// returned text.Rich returns true for Empty(), then the element shouldn't be
// changed.
type MessageUpdate interface {
MessageHeader
Author() text.Rich // optional
Content() text.Rich // optional
}
// MessageDelete is the interface for a message delete event.
type MessageDelete interface {
MessageHeader
}
// MessageAuthorAvatar is an optional interface that messages could implement. A
// frontend may optionally support this.
type MessageAuthorAvatar interface {
AuthorAvatar() (url string)
}