cchat/cchat.go

917 lines
37 KiB
Go
Raw Normal View History

// Code generated by ./cmd/internal. DO NOT EDIT.
// Package cchat is a set of stabilized interfaces for cchat implementations,
// joining the backend and frontend together.
//
//
// Backend
//
// Almost anything in the backend comes with an ID. For example, a Server must
// have an ID, or a Session must have a user ID. The backend is required to
// guarantee that IDs are somehow unique. This should already be the case for
// most chat services; for example, Discord provides IDs for guilds, channels,
// members, and more. The only time that the backend should not guarantee ID
// uniqueness is across Sessions, because it doesn't make sense to do so. In
// this case, the frontend should guarantee uniqueness instead, either by
// discarding duplicated items, overriding them, or anything reasonable and
// explicit.
//
// Methods implemented by the backend that have frontend containers as arguments
// can do IO. Frontends must NOT rely on individual backend states and should
// always assume that they will block.
//
// Methods that do not return an error must NOT do any IO to prevent blocking
Refactored to a completely new API This commit refactors entirely the ways cchat interfaces extend others. Prior to this commit, interfaces extend itself simply by implementing methods. This change is crucial to allow structs to decide whether or not an interface is extended during runtime. The current change adds the "As" methods into interfaces. When said, for example, "Messenger extends Server," we now have the Server interface implementing the AsMessenger method instead of before where the struct implementing Server also implemented Messenger's methods. For future references, these method will be called asserter methods. The biggest motivation for this change is that these asserter methods can allow backends to decide whether or not certain features are implemented during runtime. For example, not all servers may support sending messages. The asserting method is also simpler than the actual type assertions done before. Another motivation is to prepare cchat for an API that can reasonably be translated to something that can be transferred over the wire. Although the API itself will likely not be transferred over actual networking, there are lots of plans for IPC-ing the API. This could mean that developers would be able to develop the backends and frontends in any programming language. A downside to this is that the API is more restricted in terms of extending beyond interfaces defined in the package. The initial goal of this was to allow certain frontends to check for additional interfaces outside of cchat that certain services could implement. However, this goal is mostly moot, as interfaces like these require prior extensive knowledge from both the developers of the backend and frontend libraries.
2020-09-26 02:31:01 +00:00
// the main thread. As such, ID() and Name() must never do any IO. Methods that
// do return an error may do IO, but they should be documented per method.
//
// Backend implementations have certain conditions that should be adhered to:
//
// - Storing MessagesContainer and ServersContainer are advised
// against; however, they should be done if need be.
// - Other containers such as LabelContainer and IconContainer
// should also not be stored; however, the same rule as above
// applies.
// - For the server list, icon updates and such that happen after
// their calls should use SetServers().
// - For the nickname of the current server, the backend can store
// the state of the label container. It must, however, remove the
// container when the stop callback from JoinServer() is called.
// - Some methods that take in a container may take in a context as
// well. Although implementations don't have to use this context,
// it should try to.
//
// Note: IO in most cases usually refer to networking, but they should files and
// anything that is blocking, such as mutexes or semaphores.
//
// Note: As mentioned above, contexts are optional for both the frontend and
// backend. The frontend may use it for cancellation, and the backend may ignore
// it.
//
Refactored to a completely new API This commit refactors entirely the ways cchat interfaces extend others. Prior to this commit, interfaces extend itself simply by implementing methods. This change is crucial to allow structs to decide whether or not an interface is extended during runtime. The current change adds the "As" methods into interfaces. When said, for example, "Messenger extends Server," we now have the Server interface implementing the AsMessenger method instead of before where the struct implementing Server also implemented Messenger's methods. For future references, these method will be called asserter methods. The biggest motivation for this change is that these asserter methods can allow backends to decide whether or not certain features are implemented during runtime. For example, not all servers may support sending messages. The asserting method is also simpler than the actual type assertions done before. Another motivation is to prepare cchat for an API that can reasonably be translated to something that can be transferred over the wire. Although the API itself will likely not be transferred over actual networking, there are lots of plans for IPC-ing the API. This could mean that developers would be able to develop the backends and frontends in any programming language. A downside to this is that the API is more restricted in terms of extending beyond interfaces defined in the package. The initial goal of this was to allow certain frontends to check for additional interfaces outside of cchat that certain services could implement. However, this goal is mostly moot, as interfaces like these require prior extensive knowledge from both the developers of the backend and frontend libraries.
2020-09-26 02:31:01 +00:00
// Some interfaces can be extended. Interfaces that are extendable will have
// methods starting with "As" and returns another interface type. The
// implementation may or may not return the same struct as the interface, but
// the caller should not have to type assert it to a struct. They can also
// return nil, which should indicate the backend that the feature is not
// implemented.
//
Refactored to a completely new API This commit refactors entirely the ways cchat interfaces extend others. Prior to this commit, interfaces extend itself simply by implementing methods. This change is crucial to allow structs to decide whether or not an interface is extended during runtime. The current change adds the "As" methods into interfaces. When said, for example, "Messenger extends Server," we now have the Server interface implementing the AsMessenger method instead of before where the struct implementing Server also implemented Messenger's methods. For future references, these method will be called asserter methods. The biggest motivation for this change is that these asserter methods can allow backends to decide whether or not certain features are implemented during runtime. For example, not all servers may support sending messages. The asserting method is also simpler than the actual type assertions done before. Another motivation is to prepare cchat for an API that can reasonably be translated to something that can be transferred over the wire. Although the API itself will likely not be transferred over actual networking, there are lots of plans for IPC-ing the API. This could mean that developers would be able to develop the backends and frontends in any programming language. A downside to this is that the API is more restricted in terms of extending beyond interfaces defined in the package. The initial goal of this was to allow certain frontends to check for additional interfaces outside of cchat that certain services could implement. However, this goal is mostly moot, as interfaces like these require prior extensive knowledge from both the developers of the backend and frontend libraries.
2020-09-26 02:31:01 +00:00
// To avoid confusing, when said "A implements B," it is mostly assumed that A
// has a method named "AsB." It does not mean that A can be type-asserted to B.
//
Refactored to a completely new API This commit refactors entirely the ways cchat interfaces extend others. Prior to this commit, interfaces extend itself simply by implementing methods. This change is crucial to allow structs to decide whether or not an interface is extended during runtime. The current change adds the "As" methods into interfaces. When said, for example, "Messenger extends Server," we now have the Server interface implementing the AsMessenger method instead of before where the struct implementing Server also implemented Messenger's methods. For future references, these method will be called asserter methods. The biggest motivation for this change is that these asserter methods can allow backends to decide whether or not certain features are implemented during runtime. For example, not all servers may support sending messages. The asserting method is also simpler than the actual type assertions done before. Another motivation is to prepare cchat for an API that can reasonably be translated to something that can be transferred over the wire. Although the API itself will likely not be transferred over actual networking, there are lots of plans for IPC-ing the API. This could mean that developers would be able to develop the backends and frontends in any programming language. A downside to this is that the API is more restricted in terms of extending beyond interfaces defined in the package. The initial goal of this was to allow certain frontends to check for additional interfaces outside of cchat that certain services could implement. However, this goal is mostly moot, as interfaces like these require prior extensive knowledge from both the developers of the backend and frontend libraries.
2020-09-26 02:31:01 +00:00
// For future references, these "As" methods will be called asserter methods.
//
// Note: Backends must not do IO in the "As" methods. Most of the time, it
// should only conditionally check the local state and return value or nil.
//
// Below is an example of checking for an extended interface.
//
2020-09-28 03:37:27 +00:00
// if iconer := server.AsIconer(); iconer != nil {
// println("Server implements Iconer.")
// }
//
//
// Frontend
//
// Frontend contains all interfaces that a frontend can or must implement. The
// backend may call these methods any time from any goroutine. Thus, they should
// be thread-safe. They should also not block the call by doing so, as backends
// may call these methods in its own main thread.
//
// It is worth pointing out that frontend container interfaces will not have an
// error handling API, as frontends can do that themselves. Errors returned by
// backend methods will be errors from the backend itself and never the frontend
// errors.
2020-05-19 06:07:22 +00:00
package cchat
import (
"context"
"fmt"
text "github.com/diamondburned/cchat/text"
2020-05-20 03:29:53 +00:00
"io"
2020-05-19 06:07:22 +00:00
"time"
)
// ID is the type alias for an ID string. This type is used for clarification
// and documentation purposes only. Implementations could either use this type
// or a string type.
type ID = string
// Status represents a user's status. This might be used by the frontend to
// visually display the status.
type Status uint8
const (
StatusUnknown Status = iota
StatusOnline
StatusIdle
StatusBusy
StatusAway
StatusOffline
// Invisible is reserved.
StatusInvisible
)
2020-10-09 06:58:58 +00:00
func (s Status) Is(is Status) bool {
return s == is
}
// 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
Placeholder string
Description string
Secret bool
Multiline bool
}
// CompletionEntry is a single completion entry returned by CompleteMessage. The
// icon URL field is optional.
type CompletionEntry struct {
Raw string
Text text.Rich
Secondary text.Rich
IconURL string
Image bool
}
// MessageAttachment represents a single file attachment. If needed, the
// frontend will close the reader after the message is sent, that is when the
// SendMessage function returns. The backend must not use the reader after that.
type MessageAttachment struct {
io.Reader
Name string
}
// ReadIndication represents a read indication of a user/author in a messager
// server. It relates to a message ID within the server and is meant to imply
// that the user/author has read up to the given message ID.
//
// The frontend should override an existing author with the received ones. This
// could be treated as upsert operations.
type ReadIndication struct {
User User
MessageID ID
}
// 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 (e ErrInvalidConfigAtField) Error() string {
return fmt.Sprintf("Error at %s: %s", e.Key, e.Err.Error())
}
func (e ErrInvalidConfigAtField) Unwrap() error {
return e.Err
}
2020-09-28 03:37:27 +00:00
// Actioner adds custom message actions into each message. Similarly to
// ServerMessageEditor, some of these methods may do IO.
type Actioner interface {
// Do executes a message action on the given messageID, which would be taken
// from MessageHeader.ID(). This method is allowed to do IO; the frontend should
// take care of running it asynchronously.
Do(action string, id ID) error // Blocking
// MessageActions returns a list of possible actions to a message in pretty
// strings that the frontend will use to directly display. This method must not
// do IO.
2020-09-28 03:37:27 +00:00
//
// The string slice returned can be nil or empty.
Actions(id ID) []string
}
// Attacher adds attachments into the message being sent.
type Attacher interface {
2020-09-28 03:37:27 +00:00
Attachments() []MessageAttachment
2020-05-19 06:07:22 +00:00
}
// AuthenticateError is the error returned when authenticating. This error
// interface extends the normal error to allow backends to implement multi-stage
// authentication if needed in a clean way without needing any loops.
//
// This interface satisfies the error interface.
type AuthenticateError interface {
// NextStage optionally returns a slice of Authenticator interfaces if the
// authentication process requires another stage. It works similarly to
// Service's Authenticate method, both of which returns a slice of
// Authenticators.
//
// If the error returned is an actual error, and that the user should retry any
// of the authentication fields, then NextStage could return nil to signify the
// error. The frontend could reliably check nil on this field to determine
// whether or not it should recreate the authentication fields.
NextStage() []Authenticator
// Error returns the error as a string. This method makes AuthenticateError
// satisfy the built-in error interface.
Error() string
}
// The authenticator interface allows for a multistage initial authentication
// API that the backend could use. Multistage is done by calling Authenticate
// and check for AuthenticateError's NextStage method.
2020-05-19 06:07:22 +00:00
type Authenticator interface {
// Authenticate will be called with a list of values with indices correspond to
// the returned slice of AuthenticateEntry.
Authenticate([]string) (Session, AuthenticateError) // Blocking
2020-09-28 03:37:27 +00:00
// AuthenticateForm should return a list of authentication entries for the
// frontend to render.
AuthenticateForm() []AuthenticateEntry
// Description returns the description of this authenticator method.
Description() text.Rich
// Name returns a short and concise name of this Authenticator method. The name
// should not include the name of the Service.
Name() text.Rich
2020-05-19 06:07:22 +00:00
}
2020-09-28 03:37:27 +00:00
// Backlogger adds message history capabilities into a message container. The
// backend should send old messages using the MessageCreate method of the
// MessagesContainer, and the frontend should automatically sort messages based
2020-09-28 03:37:27 +00:00
// on the timestamp.
2020-05-29 07:00:00 +00:00
//
2020-09-28 03:37:27 +00:00
// As there is no stop callback, if the backend needs to fetch messages
// asynchronously, it is expected to use the context to know when to cancel.
//
2020-09-28 03:37:27 +00:00
// The frontend should usually call this method when the user scrolls to the
// top. It is expected to guarantee not to call Backlogger more than once on the
// same ID. This can usually be done by deactivating the UI.
2020-09-28 03:37:27 +00:00
//
// Note that the optional usage of contexts also apply here. The frontend should
// deactivate the UI when the backend is working. However, the frontend can
// accomodate this by not deactivating until another event is triggered, then
// freeze the UI until the method is cancelled. This works even when the backend
// does not use the context.
type Backlogger interface {
// Backlog fetches messages before the given message ID into the
2020-09-28 03:37:27 +00:00
// MessagesContainer.
//
// This method is technically a ContainerMethod, but is listed as an IOMethod
// because of the additional message ID parameter.
Backlog(ctx context.Context, before ID, msgc MessagesContainer) error // Blocking
2020-05-29 07:00:00 +00:00
}
// Commander is an optional interface that a session could implement for command
2020-05-20 03:29:53 +00:00
// support. This is different from just intercepting the SendMessage() API, as
// this extends globally to the entire session.
//
// A very primitive use of this API would be to provide additional features that
// are not in cchat through a very basic terminal interface.
2020-05-20 03:29:53 +00:00
type Commander interface {
// Run executes the given command, with the slice being already split arguments,
// similar to os.Args. The function can return both a []byte and an error value.
// The frontend should render the byte slice's value first, then display the
// error.
//
// This function can do IO.
//
// The client should make guarantees that an empty string (and thus a
// zero-length string slice) should be ignored. The backend should be able to
// assume that the argument slice is always length 1 or more.
//
//
// Words
//
// This interface and everything else inside this interface must abide by shell
// rules when splitting words. This is in contrary to the default behavior
// elsewhere, such as in Sender's Completer, where words are split by whitespace
// without care for quotes.
//
// For example, provided this string:
//
// echo "This is a string"
//
// This is the correct output:
//
// []string{"echo", "This is a string"}
//
// This is the incorrect output:
//
// []string{"echo", "\"This", "is", "a", "string\""}
//
// A helper function for this kind of behavior is available in package split,
// under the ArgsIndexed function. This implementation also provides the rough
// specifications.
Run(words []string) ([]byte, error) // Blocking
2020-09-28 03:37:27 +00:00
// Asserters.
AsCompleter() Completer // Optional
2020-05-20 03:29:53 +00:00
}
2020-09-28 03:37:27 +00:00
// Completer adds autocompletion into the message composer. IO is not allowed,
// and the backend should do that only in goroutines and update its state for
// future calls.
//
// Frontends could utilize the split package inside utils for splitting words
// and index. This is the de-facto standard implementation for splitting words,
// thus backends can rely on their behaviors.
type Completer interface {
// Complete returns the list of possible completion entries for the given word
// list and the current word index. It takes in a list of whitespace-split slice
// of string as well as the position of the cursor relative to the given string
// slice.
Complete(words []string, current int64) []CompletionEntry
}
// Configurator is an interface which the backend can implement for a primitive
// configuration API. Since these methods do return an error, they are allowed
// to do IO. The frontend should handle this appropriately, including running
// them asynchronously.
type Configurator interface {
SetConfiguration(map[string]string) error // Blocking
Configuration() (map[string]string, error) // Blocking
}
// Editor adds message editing to the messenger. Only EditMessage can do IO.
type Editor interface {
// Edit edits the message with the given ID to the given content, which is the
// edited string from RawMessageContent. This method can do IO.
Edit(id ID, content string) error // Blocking
// RawContent gets the original message text for editing. This method must not
// do IO.
RawContent(id ID) (string, error)
// IsEditable returns whether or not a message can be edited by the client. This
// method must not do IO.
IsEditable(id ID) bool
2020-09-28 03:37:27 +00:00
}
// Identifier requires ID() to return a uniquely identifiable string for
// whatever this is embedded into. Typically, servers and messages have IDs. It
// is worth mentioning that IDs should be consistent throughout the lifespan of
// the program or maybe even forever.
type Identifier interface {
ID() ID
}
// LabelContainer is a generic interface for any container that can hold texts.
// It's typically used for rich text labelling for usernames and server names.
//
// Methods that takes in a LabelContainer typically holds it in the state and
// may call SetLabel any time it wants. Thus, the frontend should synchronize
// calls with the main thread if needed.
//
// Labels given to the frontend may contain images or avatars, and the frontend
// has the choice to display them or not.
2020-09-28 03:37:27 +00:00
type LabelContainer interface {
SetLabel(text.Rich)
}
// ListMember represents a single member in the member list. This is a base
// interface that may implement more interfaces, such as Iconer for the user's
// avatar.
//
// Note that the frontend may give everyone an avatar regardless, or it may not
// show any avatars at all.
type ListMember interface {
User
Refactored to a completely new API This commit refactors entirely the ways cchat interfaces extend others. Prior to this commit, interfaces extend itself simply by implementing methods. This change is crucial to allow structs to decide whether or not an interface is extended during runtime. The current change adds the "As" methods into interfaces. When said, for example, "Messenger extends Server," we now have the Server interface implementing the AsMessenger method instead of before where the struct implementing Server also implemented Messenger's methods. For future references, these method will be called asserter methods. The biggest motivation for this change is that these asserter methods can allow backends to decide whether or not certain features are implemented during runtime. For example, not all servers may support sending messages. The asserting method is also simpler than the actual type assertions done before. Another motivation is to prepare cchat for an API that can reasonably be translated to something that can be transferred over the wire. Although the API itself will likely not be transferred over actual networking, there are lots of plans for IPC-ing the API. This could mean that developers would be able to develop the backends and frontends in any programming language. A downside to this is that the API is more restricted in terms of extending beyond interfaces defined in the package. The initial goal of this was to allow certain frontends to check for additional interfaces outside of cchat that certain services could implement. However, this goal is mostly moot, as interfaces like these require prior extensive knowledge from both the developers of the backend and frontend libraries.
2020-09-26 02:31:01 +00:00
2020-09-28 03:37:27 +00:00
// Secondary returns the subtext of this member. This could be anything, such as
// a user's custom status or away reason.
Secondary() text.Rich
// Status returns the status of the member. The backend does not have to show
// offline members with the offline status if it doesn't want to show offline
// menbers at all.
Status() Status
}
// Lister is for servers that contain children servers. This is similar to
2020-05-19 06:07:22 +00:00
// guilds containing channels in Discord, or IRC servers containing channels.
//
// There isn't a similar stop callback API unlike other interfaces because all
// servers are expected to be listed. However, they could be hidden, such as
// collapsing a tree.
//
// The backend should call both the container and other icon and label
// containers, if any.
type Lister interface {
// Servers should call SetServers() on the given ServersContainer to render all
// servers. This function can do IO, and the frontend should run this in a
// goroutine.
Servers(ServersContainer) (stop func(), err error)
// Columnate is optionally used by servers to tell the frontend whether or not
// its children should be put onto a new column instead of underneath it within
// the same tree. If the method returns false, then the frontend can treat its
// children as normal and show it as children within the same tree.
//
// For example, in Discord, guilds can be placed in guild folders, but guilds
// and guild folders are put in the same column while guilds are actually
// children of the folders. To replicate this behavior, guild folders should
// return false, and guilds should return true. Both channels and categories can
// return false.
Columnate() bool
}
2020-09-28 03:37:27 +00:00
// MemberDynamicSection represents a dynamically loaded member list section. The
// section behaves similarly to MemberSection, except the information displayed
// will be considered incomplete until LoadMore returns false.
//
// LoadLess can be called by the client to mark chunks as stale, which the
// server can then unsubscribe from.
type MemberDynamicSection interface {
// LoadLess is a method which the client must call after it is done displaying
// entries that were added from calling LoadMore.
Refactored to a completely new API This commit refactors entirely the ways cchat interfaces extend others. Prior to this commit, interfaces extend itself simply by implementing methods. This change is crucial to allow structs to decide whether or not an interface is extended during runtime. The current change adds the "As" methods into interfaces. When said, for example, "Messenger extends Server," we now have the Server interface implementing the AsMessenger method instead of before where the struct implementing Server also implemented Messenger's methods. For future references, these method will be called asserter methods. The biggest motivation for this change is that these asserter methods can allow backends to decide whether or not certain features are implemented during runtime. For example, not all servers may support sending messages. The asserting method is also simpler than the actual type assertions done before. Another motivation is to prepare cchat for an API that can reasonably be translated to something that can be transferred over the wire. Although the API itself will likely not be transferred over actual networking, there are lots of plans for IPC-ing the API. This could mean that developers would be able to develop the backends and frontends in any programming language. A downside to this is that the API is more restricted in terms of extending beyond interfaces defined in the package. The initial goal of this was to allow certain frontends to check for additional interfaces outside of cchat that certain services could implement. However, this goal is mostly moot, as interfaces like these require prior extensive knowledge from both the developers of the backend and frontend libraries.
2020-09-26 02:31:01 +00:00
//
2020-09-28 03:37:27 +00:00
// The client can call this method exactly as many times as it has called
// LoadMore. However, false should be returned if the client should stop, and
// future calls without LoadMore should still return false.
LoadLess() bool // Blocking
// LoadMore is a method which the client can call to ask for more members. This
// method can do IO.
//
// Clients may call this method on the last section in the section slice;
// however, calling this method on any section is allowed. Clients may not call
// this method if the number of members in this section is equal to Total.
LoadMore() bool // Blocking
}
2020-09-28 03:37:27 +00:00
// MemberListContainer is a generic interface for any container that can display
// a member list. This is similar to Discord's right-side member list or IRC's
// users list. Below is a visual representation of a typical member list
// container:
//
2020-09-28 03:37:27 +00:00
// +-MemberList-----------\
// | +-Section------------|
// | | |
// | | Header - Total |
// | | |
// | | +-Member-----------|
// | | | Name |
// | | | Secondary |
// | | \__________________|
// | | |
// | | +-Member-----------|
// | | | Name |
// | | | Secondary |
// | | \__________________|
// \_\____________________/
type MemberListContainer interface {
// RemoveMember removes a member from a section. If neither the member nor the
// section exists, then the client should ignore it.
RemoveMember(sectionID ID, memberID ID)
// SetMember adds or updates (or upsert) a member into a section. This operation
// must not change the section's member count. As such, changes should be done
// separately in SetSection. If the section does not exist, then the client
// should ignore this member, so, backends must call SetSections first before
// SetMember on a new section.
//
// Typically, the backend should try and avoid calling this method and instead
// update the labeler in the name. This method should only be used for adding
// members.
2020-09-28 03:37:27 +00:00
SetMember(sectionID ID, member ListMember)
// SetSections (re)sets the list of sections to be the given slice. Members from
// the old section list should be transferred over to the new section entry if
// the section name's content is the same. Old sections that don't appear in the
// new slice should be removed.
SetSections(sections []MemberSection)
}
Refactored to a completely new API This commit refactors entirely the ways cchat interfaces extend others. Prior to this commit, interfaces extend itself simply by implementing methods. This change is crucial to allow structs to decide whether or not an interface is extended during runtime. The current change adds the "As" methods into interfaces. When said, for example, "Messenger extends Server," we now have the Server interface implementing the AsMessenger method instead of before where the struct implementing Server also implemented Messenger's methods. For future references, these method will be called asserter methods. The biggest motivation for this change is that these asserter methods can allow backends to decide whether or not certain features are implemented during runtime. For example, not all servers may support sending messages. The asserting method is also simpler than the actual type assertions done before. Another motivation is to prepare cchat for an API that can reasonably be translated to something that can be transferred over the wire. Although the API itself will likely not be transferred over actual networking, there are lots of plans for IPC-ing the API. This could mean that developers would be able to develop the backends and frontends in any programming language. A downside to this is that the API is more restricted in terms of extending beyond interfaces defined in the package. The initial goal of this was to allow certain frontends to check for additional interfaces outside of cchat that certain services could implement. However, this goal is mostly moot, as interfaces like these require prior extensive knowledge from both the developers of the backend and frontend libraries.
2020-09-26 02:31:01 +00:00
// MemberLister adds a member list into a message server.
type MemberLister interface {
// ListMembers assigns the given container to the channel's member list. The
// given context may be used to provide HTTP request cancellations, but
// frontends must not rely solely on this, as the general context rules applies.
Refactored to a completely new API This commit refactors entirely the ways cchat interfaces extend others. Prior to this commit, interfaces extend itself simply by implementing methods. This change is crucial to allow structs to decide whether or not an interface is extended during runtime. The current change adds the "As" methods into interfaces. When said, for example, "Messenger extends Server," we now have the Server interface implementing the AsMessenger method instead of before where the struct implementing Server also implemented Messenger's methods. For future references, these method will be called asserter methods. The biggest motivation for this change is that these asserter methods can allow backends to decide whether or not certain features are implemented during runtime. For example, not all servers may support sending messages. The asserting method is also simpler than the actual type assertions done before. Another motivation is to prepare cchat for an API that can reasonably be translated to something that can be transferred over the wire. Although the API itself will likely not be transferred over actual networking, there are lots of plans for IPC-ing the API. This could mean that developers would be able to develop the backends and frontends in any programming language. A downside to this is that the API is more restricted in terms of extending beyond interfaces defined in the package. The initial goal of this was to allow certain frontends to check for additional interfaces outside of cchat that certain services could implement. However, this goal is mostly moot, as interfaces like these require prior extensive knowledge from both the developers of the backend and frontend libraries.
2020-09-26 02:31:01 +00:00
//
// Further behavioral documentations may be in Messenger's JoinServer method.
Refactored to a completely new API This commit refactors entirely the ways cchat interfaces extend others. Prior to this commit, interfaces extend itself simply by implementing methods. This change is crucial to allow structs to decide whether or not an interface is extended during runtime. The current change adds the "As" methods into interfaces. When said, for example, "Messenger extends Server," we now have the Server interface implementing the AsMessenger method instead of before where the struct implementing Server also implemented Messenger's methods. For future references, these method will be called asserter methods. The biggest motivation for this change is that these asserter methods can allow backends to decide whether or not certain features are implemented during runtime. For example, not all servers may support sending messages. The asserting method is also simpler than the actual type assertions done before. Another motivation is to prepare cchat for an API that can reasonably be translated to something that can be transferred over the wire. Although the API itself will likely not be transferred over actual networking, there are lots of plans for IPC-ing the API. This could mean that developers would be able to develop the backends and frontends in any programming language. A downside to this is that the API is more restricted in terms of extending beyond interfaces defined in the package. The initial goal of this was to allow certain frontends to check for additional interfaces outside of cchat that certain services could implement. However, this goal is mostly moot, as interfaces like these require prior extensive knowledge from both the developers of the backend and frontend libraries.
2020-09-26 02:31:01 +00:00
ListMembers(context.Context, MemberListContainer) (stop func(), err error)
}
2020-09-28 03:37:27 +00:00
// MemberSection represents a member list section. The section name's content
// must be unique among other sections from the same list regardless of the rich
// segments.
type MemberSection interface {
Identifier
Namer
// Total returns the total member count.
Total() int
// Asserters.
AsMemberDynamicSection() MemberDynamicSection // Optional
}
2020-09-28 03:37:27 +00:00
// MessageCreate is the interface for an incoming message.
type MessageCreate interface {
MessageHeader
// Noncer is optional.
Noncer
2020-09-28 03:37:27 +00:00
// Mentioned returns whether or not the message mentions the current user. If a
// backend does not implement mentioning, then false can be returned.
Mentioned() bool
2020-09-28 03:37:27 +00:00
Content() text.Rich
Author() User
2020-06-10 03:38:44 +00:00
}
2020-09-28 03:37:27 +00:00
// MessageDelete is the interface for a message delete event.
type MessageDelete interface {
MessageHeader
Refactored to a completely new API This commit refactors entirely the ways cchat interfaces extend others. Prior to this commit, interfaces extend itself simply by implementing methods. This change is crucial to allow structs to decide whether or not an interface is extended during runtime. The current change adds the "As" methods into interfaces. When said, for example, "Messenger extends Server," we now have the Server interface implementing the AsMessenger method instead of before where the struct implementing Server also implemented Messenger's methods. For future references, these method will be called asserter methods. The biggest motivation for this change is that these asserter methods can allow backends to decide whether or not certain features are implemented during runtime. For example, not all servers may support sending messages. The asserting method is also simpler than the actual type assertions done before. Another motivation is to prepare cchat for an API that can reasonably be translated to something that can be transferred over the wire. Although the API itself will likely not be transferred over actual networking, there are lots of plans for IPC-ing the API. This could mean that developers would be able to develop the backends and frontends in any programming language. A downside to this is that the API is more restricted in terms of extending beyond interfaces defined in the package. The initial goal of this was to allow certain frontends to check for additional interfaces outside of cchat that certain services could implement. However, this goal is mostly moot, as interfaces like these require prior extensive knowledge from both the developers of the backend and frontend libraries.
2020-09-26 02:31:01 +00:00
}
2020-09-28 03:37:27 +00:00
// MessageHeader implements the minimum interface for any message event.
type MessageHeader interface {
Identifier
Time() time.Time
}
// MessageUpdate is the interface for a message update (or edit) event. It
// behaves similarly to MessageCreate, except all fields are optional. The
// frontend is responsible for checking which field is not empty and check it.
2020-09-28 03:37:27 +00:00
type MessageUpdate interface {
MessageCreate
}
// MessagesContainer is a view implementation that displays a list of messages
// live. This implements the 3 most common message events: CreateMessage,
// UpdateMessage and DeleteMessage. The frontend must handle all 3.
//
// Since this container interface extends a single Server, the frontend is
// allowed to have multiple views. This is usually done with tabs or splits, but
// the backend should update them all nonetheless.
type MessagesContainer interface {
2020-09-28 03:37:27 +00:00
DeleteMessage(MessageDelete)
UpdateMessage(MessageUpdate)
// CreateMessage inserts a message into the container. The frontend must
// guarantee that the messages are in order based on what's returned from
// Time().
CreateMessage(MessageCreate)
}
2020-09-28 03:37:27 +00:00
// Messenger is for servers that contain messages. This is similar to Discord or
// IRC channels.
type Messenger interface {
// JoinServer joins a server that's capable of receiving messages. The server
// may not necessarily support sending messages.
//
// Frontends must never call JoinServer on the same server more than twice
// without calling the stop function first. This is the best of both worlds, as
// it greatly reduces complexity on both sides in most cases, therefore the
// backend can safely assume that there will only ever be one active JoinServer.
// If the frontend wishes to do this, it must keep its own shared message
// buffer.
2020-09-28 03:37:27 +00:00
JoinServer(context.Context, MessagesContainer) (stop func(), err error)
2020-09-28 03:37:27 +00:00
// Asserters.
2020-09-28 03:37:27 +00:00
AsSender() Sender // Optional
AsEditor() Editor // Optional
AsActioner() Actioner // Optional
AsNicknamer() Nicknamer // Optional
AsBacklogger() Backlogger // Optional
AsMemberLister() MemberLister // Optional
AsUnreadIndicator() UnreadIndicator // Optional
AsTypingIndicator() TypingIndicator // Optional
}
2020-09-28 03:37:27 +00:00
// Namer requires Name() to return the name of the object. Typically, this
// implies usernames for sessions or service names for services.
//
// Frontends can show the ID of the object when a name hasn't yet been set. The
// backend may immediately update the name afterwards, but assumptions should
// not be made.
2020-09-28 03:37:27 +00:00
type Namer interface {
// Name sets the given container to contain the name of the parent context. The
// method has no stop method; stopping is implied to be dependent on the parent
// context. As such, it's only used for updating.
Name(context.Context, LabelContainer) (stop func(), err error)
}
2020-09-28 03:37:27 +00:00
// Nicknamer adds the current user's nickname.
//
// The frontend will not traverse up the server tree, meaning the backend must
// handle nickname inheritance. This also means that servers that don't
// implement ServerMessage also don't need to implement ServerNickname. By
// default, the session name should be used.
type Nicknamer interface {
Nickname(context.Context, LabelContainer) (stop func(), err error)
}
// Noncer adds nonce support. A nonce is defined in this context as a unique
// identifier from the frontend. This interface defines the common nonce getter.
//
// Nonces are useful for frontends to know if an incoming event is a reply from
// the server backend. As such, nonces should be roundtripped through the
// server. For example, IRC would use labeled responses.
//
// The Nonce method can return an empty string. This indicates that either the
// frontend or backend (or neither) supports nonces.
//
// Contrary to other interfaces that extend with an "Is" method, the Nonce
// method could return an empty string here.
type Noncer interface {
Nonce() string
}
// ReadContainer is an interface that a frontend container can implement to show
// the read bubbles on messages. This container typically implies the message
// container, but that is up to the frontend's implementation.
type ReadContainer interface {
// DeleteIndications deletes a list of unused users/authors associated with
// their read indicators. The backend can use this to free up users/authors that
// are no longer in the server, for example when they are offline or have left
// the server.
DeleteIndications(authorIDs []ID)
// AddIndications adds a map of users/authors to the respective message ID of
// the server that implements ReadIndicator.
AddIndications([]ReadIndication)
}
// ReadIndicator adds a read indicator API for frontends to show. An example of
// the read indicator is in Matrix, where each message can have a small avatar
// indicating that the user in the room has read the message.
type ReadIndicator interface {
// ReadIndicate subscribes the given container for read activities. The backend
// must keep track of which read states to send over to not overwhelm the
// frontend, and the frontend must either keep track of them, or it should not
// display it at all.
ReadIndicate(ReadContainer) (stop func(), err error)
}
// Replier indicates that the message being sent is a reply to something.
// Frontends that support replies can assume that all messages in a Sender can
// be replied to, and the backend can choose to do nothing to the replied ID.
type Replier interface {
2021-01-01 22:28:30 +00:00
ReplyingTo() ID
}
2020-09-28 03:37:27 +00:00
// SendableMessage is the bare minimum interface of a sendable message, that is,
// a message that can be sent with SendMessage(). This allows the frontend to
// implement its own message data implementation.
//
// An example of extending this interface is MessageNonce, which is similar to
// IRCv3's labeled response extension or Discord's nonces. The frontend could
// implement this interface and check if incoming MessageCreate events implement
// the same interface.
2020-09-28 03:37:27 +00:00
type SendableMessage interface {
Content() string
// Asserters.
AsNoncer() Noncer // Optional
AsReplier() Replier // Optional
AsAttacher() Attacher // Optional
}
2020-09-28 03:37:27 +00:00
// Sender adds message sending to a messenger. Messengers that don't implement
// MessageSender will be considered read-only.
type Sender interface {
// CanAttach returns whether or not the client is allowed to upload files.
CanAttach() bool
// Send is called by the frontend to send a message to this channel.
Send(SendableMessage) error // Blocking
// Asserters.
AsCompleter() Completer // Optional
}
2020-09-28 03:37:27 +00:00
// 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.
//
// Note that the Server is allowed to implement both Lister and Messenger. This
// is useful when the messenger contains sub-servers, such as threads.
2020-09-28 03:37:27 +00:00
type Server interface {
Identifier
Namer
// Asserters.
AsLister() Lister // Optional
AsMessenger() Messenger // Optional
AsCommander() Commander // Optional
AsConfigurator() Configurator // Optional
}
// ServerUpdate represents a server update event.
2020-09-28 03:37:27 +00:00
type ServerUpdate interface {
// Server embeds a complete server. Unlike MessageUpdate, which only returns
// data on methods that are changed, ServerUpdate's methods must return the
// complete data even if they stay the same. As such, zero-value returns are
// treated as not updated, including the name.
2020-09-28 03:37:27 +00:00
Server
// PreviousID returns the ID of the item, either to be replaced or to be
// inserted in front of.
//
// If replace is true, then the returned ID is the ID of the item to be
// replaced, and the frontend should only try to use the ID as-is to find the
// old server and replace.
//
// If replace is false, then the returned ID will be the ID of the item in front
// of the embedded server. If the ID is empty or the frontend cannot find the
// server from this ID, then it should assume and prepend the server to the
// start.
PreviousID() (serverID ID, replace bool)
}
2020-09-28 03:37:27 +00:00
// ServersContainer is any type of view that displays the list of servers. It
// should implement a SetServers([]Server) that the backend could use to call
// anytime the server list changes (at all).
//
2020-09-28 03:37:27 +00:00
// Typically, most frontends should implement this interface onto a tree node,
// as servers can be infinitely nested. Frontends should also reset the entire
// node and its children when SetServers is called again.
type ServersContainer interface {
UpdateServer(ServerUpdate)
// 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.
//
// If the backend sets a nil server slice, then the frontend should take that as
// an unavailable server list rather than an empty server list. The server list
// should only be considered empty if it's an empty non-nil slice. An
// unavailable list, on the other hand, can be treated as backend issues, e.g. a
// connection issue.
2020-09-28 03:37:27 +00:00
SetServers([]Server)
}
2020-09-28 03:37:27 +00:00
// A service is a complete service that's capable of multiple sessions. It has
// to implement the Authenticate() method, which returns multiple
// implementations of Authenticator.
//
2020-09-28 03:37:27 +00:00
// A service can implement SessionRestorer, which would indicate the frontend
// that it can restore past sessions. Sessions are saved using the SessionSaver
// interface that Session can implement.
//
// A service can also implement Configurator if it has additional
// configurations. The current API is a flat key-value map, which can be parsed
// by the backend itself into more meaningful data structures. All
// configurations must be optional, as frontends may not implement a
// configurator UI.
type Service interface {
// Namer returns the name of the service.
Namer
Authenticate() []Authenticator
2020-09-28 03:37:27 +00:00
// Asserters.
AsConfigurator() Configurator // Optional
AsSessionRestorer() SessionRestorer // Optional
}
// Session is returned after authentication on the service. It implements
2020-09-28 03:37:27 +00:00
// Name(), which should return the username most of the time. It also implements
// ID(), which might be used by frontends to check against User.ID() and other
// things.
2020-09-28 03:37:27 +00:00
//
// A session can implement SessionSaver, which would allow the frontend to save
// the session into its keyring at any time. Whether the keyring is completely
// secure or not is up to the frontend. For a GTK client, that would be using
2020-09-28 03:37:27 +00:00
// the GNOME Keyring daemon.
type Session interface {
// Identifier should typically return the user ID.
Identifier
// Namer gives the name of the session, which is typically the username.
Namer
2020-09-28 03:37:27 +00:00
Lister
2020-09-28 03:37:27 +00:00
// Disconnect asks the service to disconnect. It does not necessarily mean
// removing the service.
//
// The frontend must cancel the active ServerMessage before disconnecting. The
// backend can rely on this behavior.
//
// The frontend will reuse the stored session data from SessionSaver to
// reconnect.
//
// When this function fails, the frontend may display the error upfront.
// However, it will treat the session as actually disconnected. If needed, the
// backend must implement reconnection by itself.
Disconnect() error // Blocking, Disposer
2020-09-28 03:37:27 +00:00
// Asserters.
AsCommander() Commander // Optional
AsSessionSaver() SessionSaver // Optional
}
2020-09-28 03:37:27 +00:00
// SessionRestorer extends Service and is called by the frontend to restore a
// saved session. The frontend may call this at any time, but it's usually on
// startup.
//
2020-09-28 03:37:27 +00:00
// To save a session, refer to SessionSaver.
type SessionRestorer interface {
RestoreSession(map[string]string) (Session, error) // Blocking
}
// SessionSaver extends Session and is called by the frontend to save the
// current session. This is typically called right after authentication, but a
// frontend may call this any time, including when it's closing.
//
// The frontend can ask to restore a session using SessionRestorer, which
// extends Service.
//
// The SaveSession method must not do IO; if there are any reasons that cause
// SaveSession to fail, then a nil map should be returned.
type SessionSaver interface {
SaveSession() map[string]string
}
// TypingContainer is a generic interface for any container that can display
// users typing in the current chatbox. The typing indicator must adhere to the
// TypingTimeout returned from ServerMessageTypingIndicator. The backend should
// assume that to be the case and send events appropriately.
//
// For more documentation, refer to TypingIndicator.
type TypingContainer interface {
// RemoveTyper explicitly removes the typer with the given user ID from the list
// of typers. This function is usually not needed, as the client will take care
// of removing them after TypingTimeout has been reached or other conditions
// listed in ServerMessageTypingIndicator are met.
RemoveTyper(authorID ID)
// AddTyper appends the typer (author) into the frontend's list of typers, or it
// pushes this typer on top of others. The frontend should assume current time
// every time AddTyper is called.
AddTyper(User)
2020-09-28 03:37:27 +00:00
}
// TypingIndicator optionally extends ServerMessage to provide bidirectional
// typing indicating capabilities. This is similar to typing events on Discord
// and typing client tags on IRCv3.
//
// The client should remove a typer when a message is received with the same
// user ID, when RemoveTyper() is called by the backend or when the timeout
// returned from TypingTimeout() has been reached.
type TypingIndicator interface {
// TypingSubscribe subscribes the given indicator to typing events sent by the
// backend. The added event handlers have to be removed by the backend when the
// stop() callback is called.
//
2020-09-28 03:37:27 +00:00
// This method does not take in a context, as it's supposed to only use event
// handlers and not do any IO calls. Nonetheless, the client must treat it like
// it does and call it asynchronously.
TypingSubscribe(TypingContainer) (stop func(), err error)
// TypingTimeout returns the interval between typing events sent by the client
// as well as the timeout before the client should remove the typer. Typically,
// a constant should be returned.
TypingTimeout() time.Duration
// Typing is called by the client to indicate that the user is typing. This
// function can do IO calls, and the client must take care of calling it in a
// goroutine (or an asynchronous queue) as well as throttling it to
// TypingTimeout.
Typing() error // Blocking
}
2020-09-28 03:37:27 +00:00
// UnreadContainer is an interface that a single server container (such as a
// button or a tree node) can implement if it's capable of indicating the read
// and mentioned status for that channel.
//
// Server containers that implement this has to represent unread and mentioned
// differently. For example, a mentioned channel could have a red outline, while
// an unread channel could appear brighter.
2020-09-28 03:37:27 +00:00
//
// Server containers are expected to represent this information in their parent
// nodes as well. For example, if a server is unread, then its parent servers as
// well as the session node should indicate the same status. Highlighting the
// session and service nodes are, however, implementation details, meaning that
// this decision is up to the frontend to decide.
type UnreadContainer interface {
// SetUnread sets the container's unread state to the given boolean. The
// frontend may choose how to represent this.
SetUnread(unread bool, mentioned bool)
}
// UnreadIndicator adds an unread state API for frontends to use. The unread
// state describes whether a channel has been read or not by the current user.
// It is not to be confused with ReadIndicator, which indicates the unread state
// of others.
2020-09-28 03:37:27 +00:00
type UnreadIndicator interface {
// UnreadIndicate subscribes the given unread indicator for unread and mention
// events. Examples include when a new message is arrived and the backend needs
// to indicate that it's unread.
//
// This function must provide a way to remove callbacks, as clients must call
// this when the old server is destroyed, such as when Servers is called.
UnreadIndicate(UnreadContainer) (stop func(), err error)
// MarkRead marks a message in the server messenger as read. Backends that
// implement the UnreadIndicator interface must give control of marking messages
// as read to the frontend if possible.
//
// This method is assumed to be a setter method that does not error out, because
// the frontend has no use in knowing the error. As such, marking messages as
// read is best-effort. The backend is in charge of synchronizing the read state
// with the server and coordinating it with reasonable rate limits, if needed.
MarkRead(messageID ID)
}
// User is the interface for an identifiable author. The interface defines that
// an author always have an ID and a name.
//
// An example of where this interface is used would be in MessageCreate's User
// method or embedded in Typer. The returned ID may or may not be used by the
// frontend, but backends must guarantee that the User's ID is in fact a user
// ID.
//
// The frontend may use the ID to squash messages with the same author together.
type User interface {
Identifier
Namer
}