Partial rewrite for cchat v0.5.1
This commit is contained in:
parent
7bfe466482
commit
36886b0cad
12
discord.go
12
discord.go
|
@ -1,9 +1,11 @@
|
|||
package discord
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/config"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/authenticate"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/config"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/avatar"
|
||||
"github.com/diamondburned/cchat/services"
|
||||
|
@ -29,11 +31,13 @@ type Service struct {
|
|||
empty.Service
|
||||
}
|
||||
|
||||
func (Service) Name() text.Rich {
|
||||
return text.Rich{
|
||||
func (Service) Name(_ context.Context, l cchat.LabelContainer) (func(), error) {
|
||||
l.SetLabel(text.Rich{
|
||||
Content: "Discord",
|
||||
Segments: []text.Segment{Logo},
|
||||
}
|
||||
})
|
||||
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
func (Service) Authenticate() []cchat.Authenticator {
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
package channel
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/urlutils"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
empty.Server
|
||||
shared.Channel
|
||||
commander cchat.Commander
|
||||
}
|
||||
|
||||
var _ cchat.Server = (*Channel)(nil)
|
||||
|
||||
func New(s *state.Instance, ch discord.Channel) (cchat.Server, error) {
|
||||
channel, err := NewChannel(s, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func NewChannel(s *state.Instance, ch discord.Channel) (Channel, error) {
|
||||
// Ensure the state keeps the channel's permission.
|
||||
if ch.GuildID.IsValid() {
|
||||
_, err := s.Permissions(ch.ID, s.UserID)
|
||||
if err != nil {
|
||||
return Channel{}, errors.Wrap(err, "failed to get permission")
|
||||
}
|
||||
}
|
||||
|
||||
sharedCh := shared.Channel{
|
||||
ID: ch.ID,
|
||||
GuildID: ch.GuildID,
|
||||
State: s,
|
||||
}
|
||||
|
||||
return Channel{
|
||||
Channel: sharedCh,
|
||||
commander: NewCommander(sharedCh),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ch Channel) ID() cchat.ID {
|
||||
return ch.Channel.ID.String()
|
||||
}
|
||||
|
||||
func (ch Channel) Name() text.Rich {
|
||||
c, err := ch.Self()
|
||||
if err != nil {
|
||||
return text.Rich{Content: ch.ID()}
|
||||
}
|
||||
|
||||
return text.Plain(shared.ChannelName(*c))
|
||||
}
|
||||
|
||||
func (ch Channel) AsCommander() cchat.Commander {
|
||||
return ch.commander
|
||||
}
|
||||
|
||||
func (ch Channel) AsMessenger() cchat.Messenger {
|
||||
if !ch.HasPermission(discord.PermissionViewChannel) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return message.New(ch.Channel)
|
||||
}
|
||||
|
||||
func (ch Channel) AsIconer() cchat.Iconer {
|
||||
// Guild channels never have an icon.
|
||||
if ch.GuildID.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
c, err := ch.Self()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only DM channels should have an icon.
|
||||
if c.Type != discord.DirectMessage {
|
||||
return nil
|
||||
}
|
||||
|
||||
return PresenceAvatar{
|
||||
user: c.DMRecipients[0],
|
||||
guild: ch.GuildID,
|
||||
state: ch.State,
|
||||
}
|
||||
}
|
||||
|
||||
type PresenceAvatar struct {
|
||||
user discord.User
|
||||
guild discord.GuildID
|
||||
state *state.Instance
|
||||
}
|
||||
|
||||
func (avy PresenceAvatar) Icon(ctx context.Context, iconer cchat.IconContainer) (func(), error) {
|
||||
if avy.user.Avatar != "" {
|
||||
iconer.SetIcon(urlutils.AvatarURL(avy.user.AvatarURL()))
|
||||
}
|
||||
|
||||
// There are so many other places that could be checked, but this is good
|
||||
// enough.
|
||||
|
||||
c, err := avy.state.Presence(avy.guild, avy.user.ID)
|
||||
if err == nil && c.User.Avatar != "" {
|
||||
iconer.SetIcon(urlutils.AvatarURL(c.User.AvatarURL()))
|
||||
}
|
||||
|
||||
return avy.state.AddHandler(func(update *gateway.PresenceUpdateEvent) {
|
||||
if avy.user.ID == update.User.ID {
|
||||
iconer.SetIcon(urlutils.AvatarURL(update.User.AvatarURL()))
|
||||
}
|
||||
}), nil
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package indicate
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/typer"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/config"
|
||||
)
|
||||
|
||||
type TypingIndicator struct {
|
||||
shared.Channel
|
||||
}
|
||||
|
||||
func NewTyping(ch shared.Channel) cchat.TypingIndicator {
|
||||
return TypingIndicator{ch}
|
||||
}
|
||||
|
||||
func (ti TypingIndicator) Typing() error {
|
||||
if !config.BroadcastTyping() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ti.State.Typing(ti.ID)
|
||||
}
|
||||
|
||||
// TypingTimeout returns 10 seconds.
|
||||
func (ti TypingIndicator) TypingTimeout() time.Duration {
|
||||
return 10 * time.Second
|
||||
}
|
||||
|
||||
func (ti TypingIndicator) TypingSubscribe(tc cchat.TypingContainer) (func(), error) {
|
||||
return ti.State.AddHandler(func(t *gateway.TypingStartEvent) {
|
||||
// Ignore channel mismatch or if the typing event is ours.
|
||||
if t.ChannelID != ti.ID || t.UserID == ti.State.UserID {
|
||||
return
|
||||
}
|
||||
if typer, err := typer.New(ti.State, t); err == nil {
|
||||
tc.AddTyper(typer)
|
||||
}
|
||||
}), nil
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package nickname
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
type Nicknamer struct {
|
||||
userID discord.UserID
|
||||
shared.Channel
|
||||
}
|
||||
|
||||
func New(ch shared.Channel) cchat.Nicknamer {
|
||||
return NewMember(ch.State.UserID, ch)
|
||||
}
|
||||
|
||||
func NewMember(userID discord.UserID, ch shared.Channel) cchat.Nicknamer {
|
||||
return Nicknamer{userID, ch}
|
||||
}
|
||||
|
||||
func (nn Nicknamer) Nickname(ctx context.Context, labeler cchat.LabelContainer) (func(), error) {
|
||||
// We don't have a nickname if we're not in a guild.
|
||||
if !nn.GuildID.IsValid() {
|
||||
// Use the current user.
|
||||
u, err := nn.State.Cabinet.Me()
|
||||
if err == nil {
|
||||
labeler.SetLabel(text.Plain(fmt.Sprintf("%s#%s", u.Username, u.Discriminator)))
|
||||
}
|
||||
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
nn.tryNicknameLabel(ctx, labeler)
|
||||
|
||||
return funcutil.JoinCancels(
|
||||
nn.State.AddHandler(func(chunks *gateway.GuildMembersChunkEvent) {
|
||||
if chunks.GuildID != nn.GuildID {
|
||||
return
|
||||
}
|
||||
for _, member := range chunks.Members {
|
||||
if member.User.ID == nn.userID {
|
||||
nn.setMember(labeler, member)
|
||||
break
|
||||
}
|
||||
}
|
||||
}),
|
||||
nn.State.AddHandler(func(g *gateway.GuildMemberUpdateEvent) {
|
||||
if g.GuildID == nn.GuildID && g.User.ID == nn.userID {
|
||||
nn.setMember(labeler, discord.Member{
|
||||
User: g.User,
|
||||
Nick: g.Nick,
|
||||
RoleIDs: g.RoleIDs,
|
||||
})
|
||||
}
|
||||
}),
|
||||
), nil
|
||||
}
|
||||
|
||||
func (nn Nicknamer) tryNicknameLabel(ctx context.Context, labeler cchat.LabelContainer) {
|
||||
state := nn.State.WithContext(ctx)
|
||||
|
||||
m, err := state.Cabinet.Member(nn.GuildID, nn.userID)
|
||||
if err == nil {
|
||||
nn.setMember(labeler, *m)
|
||||
}
|
||||
}
|
||||
|
||||
func (nn Nicknamer) setMember(labeler cchat.LabelContainer, m discord.Member) {
|
||||
var rich = text.Rich{Content: m.User.Username}
|
||||
if m.Nick != "" {
|
||||
rich.Content = m.Nick
|
||||
}
|
||||
|
||||
guild, err := nn.State.Cabinet.Guild(nn.GuildID)
|
||||
if err == nil {
|
||||
if color := discord.MemberColor(*guild, m); color > 0 {
|
||||
rich.Segments = []text.Segment{
|
||||
colored.New(len(rich.Content), color.Uint32()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
labeler.SetLabel(rich)
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
package folder
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/guild"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
)
|
||||
|
||||
type GuildFolder struct {
|
||||
empty.Server
|
||||
gateway.GuildFolder
|
||||
state *state.Instance
|
||||
}
|
||||
|
||||
func New(s *state.Instance, gf gateway.GuildFolder) cchat.Server {
|
||||
// Name should never be empty.
|
||||
if gf.Name == "" {
|
||||
var names = make([]string, 0, len(gf.GuildIDs))
|
||||
|
||||
for _, id := range gf.GuildIDs {
|
||||
g, err := s.Cabinet.Guild(id)
|
||||
if err == nil {
|
||||
names = append(names, g.Name)
|
||||
}
|
||||
}
|
||||
|
||||
gf.Name = strings.Join(names, ", ")
|
||||
}
|
||||
|
||||
return &GuildFolder{
|
||||
GuildFolder: gf,
|
||||
state: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (gf *GuildFolder) ID() cchat.ID {
|
||||
return strconv.FormatInt(int64(gf.GuildFolder.ID), 10)
|
||||
}
|
||||
|
||||
func (gf *GuildFolder) Name() text.Rich {
|
||||
var name = text.Rich{
|
||||
// 1en space for style.
|
||||
Content: gf.GuildFolder.Name,
|
||||
}
|
||||
|
||||
if gf.GuildFolder.Color > 0 {
|
||||
name.Segments = []text.Segment{
|
||||
// The length of this black box is actually 3. Mind == blown.
|
||||
colored.New(len(name.Content), gf.GuildFolder.Color.Uint32()),
|
||||
}
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// IsLister returns true.
|
||||
func (gf *GuildFolder) AsLister() cchat.Lister { return gf }
|
||||
|
||||
func (gf *GuildFolder) Servers(container cchat.ServersContainer) error {
|
||||
var servers = make([]cchat.Server, 0, len(gf.GuildIDs))
|
||||
|
||||
for _, id := range gf.GuildIDs {
|
||||
g, err := gf.state.Cabinet.Guild(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
servers = append(servers, guild.New(gf.state, g))
|
||||
}
|
||||
|
||||
container.SetServers(servers)
|
||||
return nil
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
package guild
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/category"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/urlutils"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Guild struct {
|
||||
empty.Server
|
||||
id discord.GuildID
|
||||
state *state.Instance
|
||||
}
|
||||
|
||||
func New(s *state.Instance, g *discord.Guild) cchat.Server {
|
||||
return &Guild{
|
||||
id: g.ID,
|
||||
state: s,
|
||||
}
|
||||
}
|
||||
|
||||
func NewFromID(s *state.Instance, gID discord.GuildID) (cchat.Server, error) {
|
||||
g, err := s.Cabinet.Guild(gID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return New(s, g), nil
|
||||
}
|
||||
|
||||
func (g *Guild) self() (*discord.Guild, error) {
|
||||
return g.state.Cabinet.Guild(g.id)
|
||||
}
|
||||
|
||||
func (g *Guild) ID() cchat.ID {
|
||||
return g.id.String()
|
||||
}
|
||||
|
||||
func (g *Guild) Name() text.Rich {
|
||||
s, err := g.self()
|
||||
if err != nil {
|
||||
// This shouldn't happen.
|
||||
return text.Rich{Content: g.id.String()}
|
||||
}
|
||||
|
||||
return text.Rich{Content: s.Name}
|
||||
}
|
||||
|
||||
func (g *Guild) AsIconer() cchat.Iconer { return g }
|
||||
|
||||
func (g *Guild) Icon(ctx context.Context, iconer cchat.IconContainer) (func(), error) {
|
||||
s, err := g.self()
|
||||
if err != nil {
|
||||
// This shouldn't happen.
|
||||
return nil, errors.Wrap(err, "Failed to get guild")
|
||||
}
|
||||
|
||||
// Used for comparison.
|
||||
if s.Icon != "" {
|
||||
iconer.SetIcon(urlutils.AvatarURL(s.IconURL()))
|
||||
}
|
||||
|
||||
return g.state.AddHandler(func(update *gateway.GuildUpdateEvent) {
|
||||
if g.id == update.ID {
|
||||
iconer.SetIcon(urlutils.AvatarURL(s.IconURL()))
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (g *Guild) AsLister() cchat.Lister { return g }
|
||||
|
||||
func (g *Guild) Servers(container cchat.ServersContainer) error {
|
||||
c, err := g.state.Channels(g.id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to get channels")
|
||||
}
|
||||
|
||||
// Only get top-level channels (those with category ID being null).
|
||||
var toplevels = category.FilterAccessible(g.state, category.FilterCategory(c, 0))
|
||||
|
||||
// Sort so that positions are correct.
|
||||
sort.SliceStable(toplevels, func(i, j int) bool {
|
||||
return toplevels[i].Position < toplevels[j].Position
|
||||
})
|
||||
|
||||
// Sort so that channels are before categories.
|
||||
sort.SliceStable(toplevels, func(i, _ int) bool {
|
||||
return toplevels[i].Type != discord.GuildCategory
|
||||
})
|
||||
|
||||
chs := make([]cchat.Server, 0, len(toplevels))
|
||||
ids := make(map[discord.ChannelID]struct{}, len(toplevels))
|
||||
|
||||
for _, ch := range toplevels {
|
||||
switch ch.Type {
|
||||
case discord.GuildCategory:
|
||||
chs = append(chs, category.New(g.state, ch))
|
||||
case discord.GuildText:
|
||||
c, err := channel.New(g.state, ch)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Failed to make channel %q: %v", ch.Name, err)
|
||||
}
|
||||
chs = append(chs, c)
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
container.SetServers(chs)
|
||||
|
||||
// TODO: account for insertion/deletion.
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/arikawa/v2/session"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/folder"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/guild"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/private"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/urlutils"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
"github.com/diamondburned/ningen/v2"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var ErrMFA = session.ErrMFA
|
||||
|
||||
type Session struct {
|
||||
empty.Session
|
||||
private cchat.Server
|
||||
state *state.Instance
|
||||
}
|
||||
|
||||
func NewFromInstance(i *state.Instance) (cchat.Session, error) {
|
||||
priv, err := private.New(i)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to make main private server")
|
||||
}
|
||||
|
||||
return &Session{
|
||||
private: priv,
|
||||
state: i,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Session) ID() cchat.ID {
|
||||
return s.state.UserID.String()
|
||||
}
|
||||
|
||||
func (s *Session) Name() text.Rich {
|
||||
u, err := s.state.Cabinet.Me()
|
||||
if err != nil {
|
||||
// This shouldn't happen, ever.
|
||||
return text.Rich{Content: "<@" + s.state.UserID.String() + ">"}
|
||||
}
|
||||
|
||||
return text.Rich{Content: u.Username + "#" + u.Discriminator}
|
||||
}
|
||||
|
||||
func (s *Session) AsIconer() cchat.Iconer { return s }
|
||||
|
||||
func (s *Session) Icon(ctx context.Context, iconer cchat.IconContainer) (func(), error) {
|
||||
u, err := s.state.Me()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Failed to get the current user")
|
||||
}
|
||||
|
||||
// Thanks to arikawa, AvatarURL is never empty.
|
||||
iconer.SetIcon(urlutils.AvatarURL(u.AvatarURL()))
|
||||
|
||||
return s.state.AddHandler(func(*gateway.UserUpdateEvent) {
|
||||
// Bypass the event and use the state cache.
|
||||
if u, err := s.state.Cabinet.Me(); err == nil {
|
||||
iconer.SetIcon(urlutils.AvatarURL(u.AvatarURL()))
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Session) Disconnect() error {
|
||||
return s.state.Close()
|
||||
}
|
||||
|
||||
func (s *Session) AsSessionSaver() cchat.SessionSaver { return s.state }
|
||||
|
||||
func (s *Session) Servers(container cchat.ServersContainer) error {
|
||||
// Reset the entire container when the session is closed.
|
||||
s.state.AddHandler(func(*session.Closed) {
|
||||
container.SetServers(nil)
|
||||
})
|
||||
|
||||
// Set the entire container again once reconnected.
|
||||
s.state.AddHandler(func(*ningen.Connected) {
|
||||
s.servers(container)
|
||||
})
|
||||
|
||||
return s.servers(container)
|
||||
}
|
||||
|
||||
func (s *Session) servers(container cchat.ServersContainer) error {
|
||||
ready := s.state.Ready()
|
||||
|
||||
// If the user has guild folders:
|
||||
if len(ready.UserSettings.GuildFolders) > 0 {
|
||||
// TODO: account for missing guilds.
|
||||
toplevels := make([]cchat.Server, 1, len(ready.UserSettings.GuildFolders)+1)
|
||||
toplevels[0] = s.private
|
||||
|
||||
for _, guildFolder := range ready.UserSettings.GuildFolders {
|
||||
// TODO: correct.
|
||||
switch {
|
||||
case guildFolder.ID != 0:
|
||||
fallthrough
|
||||
case len(guildFolder.GuildIDs) > 1:
|
||||
toplevels = append(toplevels, folder.New(s.state, guildFolder))
|
||||
|
||||
case len(guildFolder.GuildIDs) == 1:
|
||||
g, err := guild.NewFromID(s.state, guildFolder.GuildIDs[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
toplevels = append(toplevels, g)
|
||||
}
|
||||
}
|
||||
|
||||
container.SetServers(toplevels)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the user doesn't have guild folders but has sorted their guilds
|
||||
// before:
|
||||
if len(ready.UserSettings.GuildPositions) > 0 {
|
||||
guilds := make([]cchat.Server, 1, len(ready.UserSettings.GuildPositions)+1)
|
||||
guilds[0] = s.private
|
||||
|
||||
for _, id := range ready.UserSettings.GuildPositions {
|
||||
g, err := guild.NewFromID(s.state, id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
guilds = append(guilds, g)
|
||||
}
|
||||
|
||||
container.SetServers(guilds)
|
||||
return nil
|
||||
}
|
||||
|
||||
// None of the above:
|
||||
g, err := s.state.Guilds()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
servers := make([]cchat.Server, len(g)+1)
|
||||
servers[0] = s.private
|
||||
|
||||
for i := range g {
|
||||
servers[i+1] = guild.New(s.state, &g[i])
|
||||
}
|
||||
|
||||
container.SetServers(servers)
|
||||
return nil
|
||||
}
|
|
@ -1,116 +0,0 @@
|
|||
// Package state provides a shared state instance for other packages to use.
|
||||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/session"
|
||||
"github.com/diamondburned/arikawa/v2/state"
|
||||
"github.com/diamondburned/arikawa/v2/state/store/defaultstore"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state/nonce"
|
||||
"github.com/diamondburned/ningen/v2"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
*ningen.State
|
||||
Nonces *nonce.Map
|
||||
|
||||
// UserID is a constant user ID of the current user. It is guaranteed to be
|
||||
// valid.
|
||||
UserID discord.UserID
|
||||
}
|
||||
|
||||
var _ cchat.SessionSaver = (*Instance)(nil)
|
||||
|
||||
// ErrInvalidSession is returned if SessionRestore is given a bad session.
|
||||
var ErrInvalidSession = errors.New("invalid session")
|
||||
|
||||
func NewFromData(data map[string]string) (*Instance, error) {
|
||||
tk, ok := data["token"]
|
||||
if !ok {
|
||||
return nil, ErrInvalidSession
|
||||
}
|
||||
|
||||
return NewFromToken(tk)
|
||||
}
|
||||
|
||||
func NewFromToken(token string) (*Instance, error) {
|
||||
s, err := state.New(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return New(s)
|
||||
}
|
||||
|
||||
func Login(email, password, mfa string) (*Instance, error) {
|
||||
session, err := session.Login(email, password, mfa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cabinet := defaultstore.New()
|
||||
cabinet.MessageStore = defaultstore.NewMessage(50)
|
||||
|
||||
return New(state.NewFromSession(session, cabinet))
|
||||
}
|
||||
|
||||
func New(s *state.State) (*Instance, error) {
|
||||
n, err := ningen.FromState(s)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create a state wrapper")
|
||||
}
|
||||
|
||||
// n.Client.OnRequest = append(n.Client.OnRequest,
|
||||
// func(r httpdriver.Request) error {
|
||||
// log.Println("[Discord] Request", r.GetPath())
|
||||
// return nil
|
||||
// },
|
||||
// )
|
||||
|
||||
if err := n.Open(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prefetch user.
|
||||
u, err := s.Me()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get current user")
|
||||
}
|
||||
|
||||
return &Instance{
|
||||
UserID: u.ID,
|
||||
State: n,
|
||||
Nonces: new(nonce.Map),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Permissions queries for the permission without hitting the REST API.
|
||||
func (s *Instance) Permissions(
|
||||
chID discord.ChannelID, uID discord.UserID) (discord.Permissions, error) {
|
||||
|
||||
return s.StateOnly().Permissions(chID, uID)
|
||||
}
|
||||
|
||||
var deadCtx = expiredContext()
|
||||
|
||||
// StateOnly returns a shallow copy of *State with an already-expired context.
|
||||
func (s *Instance) StateOnly() *state.State {
|
||||
return s.WithContext(deadCtx)
|
||||
}
|
||||
|
||||
func (s *Instance) SaveSession() map[string]string {
|
||||
return map[string]string{
|
||||
"token": s.Token,
|
||||
}
|
||||
}
|
||||
|
||||
func expiredContext() context.Context {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
return ctx
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type boolStamp struct {
|
||||
stamp time.Duration
|
||||
value bool
|
||||
}
|
||||
|
||||
var _ customType = (*boolStamp)(nil)
|
||||
|
||||
func (bs boolStamp) Marshal() string {
|
||||
if bs.stamp > 0 {
|
||||
return bs.stamp.String()
|
||||
}
|
||||
|
||||
return strconv.FormatBool(bs.value)
|
||||
}
|
||||
|
||||
func (bs *boolStamp) Unmarshal(v string) error {
|
||||
t, err := time.ParseDuration(v)
|
||||
if err == nil && t > 0 {
|
||||
bs.stamp = t
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err == nil {
|
||||
bs.value = b
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.New("invalid bool or timestamp")
|
||||
}
|
|
@ -9,21 +9,9 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var World = ®istry{
|
||||
configs: []config{
|
||||
{"Mention on Reply", true},
|
||||
{"Broadcast Typing", true},
|
||||
},
|
||||
}
|
||||
|
||||
// MentionOnReply returns true if message replies should mention users.
|
||||
func MentionOnReply() bool {
|
||||
return World.get(0).(bool)
|
||||
}
|
||||
|
||||
// BroadcastTyping returns true if typing events should be broadcasted.
|
||||
func BroadcastTyping() bool {
|
||||
return World.get(1).(bool)
|
||||
type customType interface {
|
||||
Marshal() string
|
||||
Unmarshal(string) error
|
||||
}
|
||||
|
||||
type config struct {
|
||||
|
@ -55,13 +43,14 @@ func (c *config) Unmarshal(src map[string]string) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
var v interface{}
|
||||
|
||||
switch c.Value.(type) {
|
||||
switch v := c.Value.(type) {
|
||||
case bool:
|
||||
v, err = strconv.ParseBool(strVal)
|
||||
c.Value, err = strconv.ParseBool(strVal)
|
||||
case string:
|
||||
v = strVal
|
||||
c.Value = strVal
|
||||
case customType:
|
||||
err = v.Unmarshal(strVal)
|
||||
c.Value = v
|
||||
default:
|
||||
err = fmt.Errorf("unknown type %T", c.Value)
|
||||
}
|
||||
|
@ -73,7 +62,6 @@ func (c *config) Unmarshal(src map[string]string) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
c.Value = v
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/diamondburned/cchat"
|
||||
)
|
||||
|
||||
var World cchat.Configurator = world
|
||||
|
||||
var world = ®istry{
|
||||
configs: []config{
|
||||
{"Mention on Reply", &boolStamp{stamp: 5 * time.Minute, value: false}},
|
||||
{"Broadcast Typing", true},
|
||||
},
|
||||
}
|
||||
|
||||
// MentionOnReply returns true if message replies should mention users.
|
||||
func MentionOnReply(timestamp time.Time) bool {
|
||||
v := world.get(0).(boolStamp)
|
||||
|
||||
if v.stamp > 0 {
|
||||
return timestamp.Add(v.stamp).Before(time.Now())
|
||||
}
|
||||
|
||||
return v.value
|
||||
}
|
||||
|
||||
// BroadcastTyping returns true if typing events should be broadcasted.
|
||||
func BroadcastTyping() bool {
|
||||
return world.get(1).(bool)
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
package message
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
|
@ -13,22 +14,26 @@ import (
|
|||
)
|
||||
|
||||
type Author struct {
|
||||
name text.Rich
|
||||
user *mention.User // same pointer as in name
|
||||
name text.Rich
|
||||
user *mention.User // same pointer as in name
|
||||
state *state.Instance
|
||||
}
|
||||
|
||||
var _ cchat.User = (*Author)(nil)
|
||||
|
||||
// NewAuthor creates a new message author.
|
||||
func NewAuthor(user *mention.User) Author {
|
||||
func NewAuthor(s *state.Instance, user *mention.User) Author {
|
||||
user.WithState(s.State)
|
||||
|
||||
return Author{
|
||||
name: RenderAuthorName(user),
|
||||
user: user,
|
||||
name: RenderUserName(user),
|
||||
user: user,
|
||||
state: s,
|
||||
}
|
||||
}
|
||||
|
||||
// RenderAuthorName renders the given user mention into a text segment.
|
||||
func RenderAuthorName(user *mention.User) text.Rich {
|
||||
// RenderUserName renders the given user mention into a text segment.
|
||||
func RenderUserName(user *mention.User) text.Rich {
|
||||
var rich text.Rich
|
||||
richUser(&rich, user)
|
||||
return rich
|
||||
|
@ -46,7 +51,11 @@ func richUser(rich *text.Rich, user *mention.User) (start, end int) {
|
|||
)
|
||||
}
|
||||
|
||||
rich.Segments = append(rich.Segments, mention.NewSegment(start, end, user))
|
||||
rich.Segments = append(rich.Segments, mention.Segment{
|
||||
Start: start,
|
||||
End: end,
|
||||
User: user,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -55,17 +64,18 @@ func (a Author) ID() cchat.ID {
|
|||
return a.user.UserID().String()
|
||||
}
|
||||
|
||||
func (a Author) Name() text.Rich {
|
||||
return a.name
|
||||
}
|
||||
// Name subscribes the author to the global name label registry.
|
||||
func (a Author) Name(_ context.Context, l cchat.LabelContainer) (func(), error) {
|
||||
if guildID := a.user.GuildID(); guildID.IsValid() {
|
||||
return a.state.Labels.AddMemberLabel(guildID, a.user.UserID(), l), nil
|
||||
}
|
||||
|
||||
func (a Author) Avatar() string {
|
||||
return a.user.Avatar()
|
||||
return a.state.Labels.AddPresenceLabel(a.user.UserID(), l), nil
|
||||
}
|
||||
|
||||
const authorReplyingTo = " replying to "
|
||||
|
||||
// AddUserReply modifies Author to make it appear like it's a message reply.
|
||||
// AddUserReply modifies User to make it appear like it's a message reply.
|
||||
// Specifically, this function is used for direct messages in virtual channels.
|
||||
func (a *Author) AddUserReply(user discord.User, s *state.Instance) {
|
||||
a.name.Content += authorReplyingTo
|
||||
|
@ -87,7 +97,7 @@ func (a *Author) AddChannelReply(ch discord.Channel, s *state.Instance) {
|
|||
}
|
||||
|
||||
a.name.Content += authorReplyingTo
|
||||
start, end := segutil.Write(&a.name, shared.ChannelName(ch))
|
||||
start, end := segutil.Write(&a.name, mention.ChannelName(ch))
|
||||
|
||||
a.name.Segments = append(a.name.Segments,
|
||||
mention.Segment{
|
||||
|
@ -98,7 +108,7 @@ func (a *Author) AddChannelReply(ch discord.Channel, s *state.Instance) {
|
|||
)
|
||||
}
|
||||
|
||||
// AddMessageReference adds a message reference to the author.
|
||||
// AddMessageReference adds a message reference to the user.
|
||||
func (a *Author) AddMessageReference(ref discord.Message, s *state.Instance) {
|
||||
a.name.Content += authorReplyingTo
|
||||
|
|
@ -100,7 +100,7 @@ func NewGuildMessageCreate(c *gateway.MessageCreateEvent, s *state.Instance) Mes
|
|||
|
||||
user.Prefetch()
|
||||
|
||||
return NewMessage(message, s, NewAuthor(user))
|
||||
return NewMessage(message, s, NewAuthor(s, user))
|
||||
}
|
||||
|
||||
// NewBacklogMessage uses the session to create a message fetched from the
|
||||
|
@ -118,7 +118,7 @@ func NewBacklogMessage(m discord.Message, s *state.Instance) Message {
|
|||
user.WithState(s.State)
|
||||
user.Prefetch()
|
||||
|
||||
return NewMessage(m, s, NewAuthor(user))
|
||||
return NewMessage(m, s, NewAuthor(s, user))
|
||||
}
|
||||
|
||||
// NewDirectMessage creates a new direct message.
|
||||
|
@ -127,7 +127,7 @@ func NewDirectMessage(m discord.Message, s *state.Instance) Message {
|
|||
user.WithState(s.State)
|
||||
user.Prefetch()
|
||||
|
||||
return NewMessage(m, s, NewAuthor(user))
|
||||
return NewMessage(m, s, NewAuthor(s, user))
|
||||
}
|
||||
|
||||
// NewAuthorUpdate creates a new message that contains a new author.
|
||||
|
@ -137,7 +137,7 @@ func NewAuthorUpdate(msg discord.Message, m discord.Member, s *state.Instance) M
|
|||
user.WithGuildID(msg.GuildID)
|
||||
user.WithMember(m)
|
||||
|
||||
author := NewAuthor(user)
|
||||
author := NewAuthor(s, user)
|
||||
if ref := ReferencedMessage(msg, s, true); ref != nil {
|
||||
author.AddMessageReference(*ref, s)
|
||||
}
|
||||
|
@ -303,7 +303,7 @@ func newRegularContent(m discord.Message, s *state.Instance) Message {
|
|||
}
|
||||
}
|
||||
|
||||
func (m Message) Author() cchat.Author {
|
||||
func (m Message) Author() cchat.User {
|
||||
if m.author.user == nil {
|
||||
return nil
|
||||
}
|
||||
|
@ -374,7 +374,11 @@ func segmentFuncFromMention(m discord.Message, s *state.Instance) func(i, j int)
|
|||
|
||||
user.Prefetch()
|
||||
|
||||
return mention.NewSegment(i, j, user)
|
||||
return mention.Segment{
|
||||
Start: i,
|
||||
End: j,
|
||||
User: user,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
package category
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -76,24 +76,16 @@ func (c *Category) ID() cchat.ID {
|
|||
return c.id.String()
|
||||
}
|
||||
|
||||
func (c *Category) Name() text.Rich {
|
||||
t, err := c.state.Channel(c.id)
|
||||
if err != nil {
|
||||
// This shouldn't happen.
|
||||
return text.Rich{Content: c.id.String()}
|
||||
}
|
||||
|
||||
return text.Rich{
|
||||
Content: t.Name,
|
||||
}
|
||||
func (c *Category) Name(_ context.Context, l cchat.LabelContainer) (func(), error) {
|
||||
return c.state.Labels.AddChannelLabel(c.id, l), nil
|
||||
}
|
||||
|
||||
func (c *Category) AsLister() cchat.Lister { return c }
|
||||
|
||||
func (c *Category) Servers(container cchat.ServersContainer) error {
|
||||
func (c *Category) Servers(container cchat.ServersContainer) (func(), error) {
|
||||
t, err := c.state.Channels(c.guildID)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to get channels")
|
||||
return nil, errors.Wrap(err, "Failed to get channels")
|
||||
}
|
||||
|
||||
// Filter out channels with this category ID.
|
||||
|
@ -107,12 +99,12 @@ func (c *Category) Servers(container cchat.ServersContainer) error {
|
|||
for i := range chs {
|
||||
c, err := channel.New(c.state, chs[i])
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Failed to make channel %s: %v", chs[i].Name, err)
|
||||
return nil, errors.Wrapf(err, "Failed to make channel %s: %v", chs[i].Name, err)
|
||||
}
|
||||
|
||||
chv[i] = c
|
||||
}
|
||||
|
||||
container.SetServers(chv)
|
||||
return nil
|
||||
return func() {}, nil
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package channel
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
empty.Server
|
||||
shared.Channel
|
||||
commander cchat.Commander
|
||||
}
|
||||
|
||||
var _ cchat.Server = (*Channel)(nil)
|
||||
|
||||
func New(s *state.Instance, ch discord.Channel) (cchat.Server, error) {
|
||||
channel, err := NewChannel(s, ch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
func NewChannel(s *state.Instance, ch discord.Channel) (Channel, error) {
|
||||
// Ensure the state keeps the channel's permission.
|
||||
if ch.GuildID.IsValid() {
|
||||
_, err := s.Permissions(ch.ID, s.UserID)
|
||||
if err != nil {
|
||||
return Channel{}, errors.Wrap(err, "failed to get permission")
|
||||
}
|
||||
}
|
||||
|
||||
sharedCh := shared.Channel{
|
||||
ID: ch.ID,
|
||||
GuildID: ch.GuildID,
|
||||
State: s,
|
||||
}
|
||||
|
||||
return Channel{
|
||||
Channel: sharedCh,
|
||||
commander: NewCommander(sharedCh),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ch Channel) ID() cchat.ID {
|
||||
return ch.Channel.ID.String()
|
||||
}
|
||||
|
||||
func (ch Channel) Name(_ context.Context, l cchat.LabelContainer) (func(), error) {
|
||||
return ch.State.Labels.AddChannelLabel(ch.Channel.ID, l), nil
|
||||
}
|
||||
|
||||
func (ch Channel) AsCommander() cchat.Commander {
|
||||
return ch.commander
|
||||
}
|
||||
|
||||
func (ch Channel) AsMessenger() cchat.Messenger {
|
||||
if !ch.HasPermission(discord.PermissionViewChannel) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return messenger.New(ch.Channel)
|
||||
}
|
|
@ -4,21 +4,21 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/commands"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send/complete"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/commands"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/sender/completer"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
type Commander struct {
|
||||
shared.Channel
|
||||
msgCompl complete.ChannelCompleter
|
||||
msgCompl completer.ChannelCompleter
|
||||
}
|
||||
|
||||
func NewCommander(ch shared.Channel) cchat.Commander {
|
||||
return Commander{
|
||||
Channel: ch,
|
||||
msgCompl: complete.ChannelCompleter{
|
||||
msgCompl: completer.ChannelCompleter{
|
||||
Channel: ch,
|
||||
},
|
||||
}
|
|
@ -3,7 +3,7 @@ package commands
|
|||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
)
|
||||
|
||||
type Command struct {
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
"github.com/diamondburned/arikawa/v2/bot/extras/arguments"
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package action
|
||||
package actioner
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package backlog
|
||||
package backlogger
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/message"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
package edit
|
||||
package editor
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
|
@ -1,26 +1,53 @@
|
|||
package typer
|
||||
package indicator
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/config"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/message"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Typer struct {
|
||||
message.Author
|
||||
time discord.UnixTimestamp
|
||||
type TypingIndicator struct {
|
||||
shared.Channel
|
||||
}
|
||||
|
||||
var _ cchat.Typer = (*Typer)(nil)
|
||||
func NewTyping(ch shared.Channel) cchat.TypingIndicator {
|
||||
return TypingIndicator{ch}
|
||||
}
|
||||
|
||||
func (ti TypingIndicator) Typing() error {
|
||||
if !config.BroadcastTyping() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ti.State.Typing(ti.ID)
|
||||
}
|
||||
|
||||
// TypingTimeout returns 10 seconds.
|
||||
func (ti TypingIndicator) TypingTimeout() time.Duration {
|
||||
return 10 * time.Second
|
||||
}
|
||||
|
||||
func (ti TypingIndicator) TypingSubscribe(tc cchat.TypingContainer) (func(), error) {
|
||||
return ti.State.AddHandler(func(t *gateway.TypingStartEvent) {
|
||||
// Ignore channel mismatch or if the typing event is ours.
|
||||
if t.ChannelID != ti.ID || t.UserID == ti.State.UserID {
|
||||
return
|
||||
}
|
||||
if typer, err := NewTyperUser(ti.State, t); err == nil {
|
||||
tc.AddTyper(typer)
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
// New creates a new Typer that satisfies cchat.Typer.
|
||||
func New(s *state.Instance, ev *gateway.TypingStartEvent) (*Typer, error) {
|
||||
func NewTyperUser(s *state.Instance, ev *gateway.TypingStartEvent) (cchat.User, error) {
|
||||
var user *mention.User
|
||||
|
||||
if ev.GuildID.IsValid() {
|
||||
|
@ -56,12 +83,5 @@ func New(s *state.Instance, ev *gateway.TypingStartEvent) (*Typer, error) {
|
|||
user.WithState(s.State)
|
||||
user.Prefetch()
|
||||
|
||||
return &Typer{
|
||||
Author: message.NewAuthor(user),
|
||||
time: ev.Timestamp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t Typer) Time() time.Time {
|
||||
return t.time.Time()
|
||||
return message.NewAuthor(s, user), nil
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package indicate
|
||||
package indicator
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/diamondburned/ningen/v2/states/read"
|
||||
"github.com/pkg/errors"
|
||||
)
|
|
@ -1,4 +1,4 @@
|
|||
package memberlist
|
||||
package memberlister
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/emoji"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
|
@ -40,18 +40,21 @@ func (l *Member) ID() cchat.ID {
|
|||
return l.mention.UserID().String()
|
||||
}
|
||||
|
||||
func (l *Member) Name(ctx context.Context, labeler cchat.LabelContainer) error {
|
||||
func (l *Member) Name(ctx context.Context, labeler cchat.LabelContainer) (func(), error) {
|
||||
l.mention.Prefetch()
|
||||
content := l.mention.DisplayName()
|
||||
|
||||
labeler.SetLabel(text.Rich{
|
||||
Content: content,
|
||||
Segments: []text.Segment{
|
||||
mention.NewSegment(0, len(content), &l.mention),
|
||||
mention.Segment{
|
||||
End: len(content),
|
||||
User: &l.mention,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
func (l *Member) Status() cchat.Status {
|
|
@ -1,11 +1,11 @@
|
|||
package memberlist
|
||||
package memberlister
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/diamondburned/ningen/v2/states/member"
|
||||
)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package memberlist
|
||||
package memberlister
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
|
@ -61,9 +61,9 @@ func (s Section) ID() cchat.ID {
|
|||
return s.id
|
||||
}
|
||||
|
||||
func (s Section) Name(ctx context.Context, labeler cchat.LabelContainer) (err error) {
|
||||
func (s Section) Name(ctx context.Context, labeler cchat.LabelContainer) (func(), error) {
|
||||
labeler.SetLabel(text.Plain(s.name))
|
||||
return nil
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
func (s Section) Total() int {
|
|
@ -1,4 +1,4 @@
|
|||
package message
|
||||
package messenger
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -7,15 +7,14 @@ import (
|
|||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/action"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/backlog"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/edit"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/indicate"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/memberlist"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/nickname"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/message"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/actioner"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/backlogger"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/editor"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/indicator"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/memberlister"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/sender"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
)
|
||||
|
@ -107,7 +106,7 @@ func (msgr *Messenger) AsSender() cchat.Sender {
|
|||
return nil
|
||||
}
|
||||
|
||||
return send.New(msgr.Channel)
|
||||
return sender.New(msgr.Channel)
|
||||
}
|
||||
|
||||
func (msgr *Messenger) AsEditor() cchat.Editor {
|
||||
|
@ -115,22 +114,22 @@ func (msgr *Messenger) AsEditor() cchat.Editor {
|
|||
return nil
|
||||
}
|
||||
|
||||
return edit.New(msgr.Channel)
|
||||
return editor.New(msgr.Channel)
|
||||
}
|
||||
|
||||
func (msgr *Messenger) AsActioner() cchat.Actioner {
|
||||
return action.New(msgr.Channel)
|
||||
return actioner.New(msgr.Channel)
|
||||
}
|
||||
|
||||
func (msgr *Messenger) AsNicknamer() cchat.Nicknamer {
|
||||
return nickname.New(msgr.Channel)
|
||||
return NewMeNicknamer(msgr.Channel)
|
||||
}
|
||||
|
||||
func (msgr *Messenger) AsMemberLister() cchat.MemberLister {
|
||||
if !msgr.GuildID.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return memberlist.New(msgr.Channel)
|
||||
return memberlister.New(msgr.Channel)
|
||||
}
|
||||
|
||||
func (msgr *Messenger) AsBacklogger() cchat.Backlogger {
|
||||
|
@ -138,13 +137,13 @@ func (msgr *Messenger) AsBacklogger() cchat.Backlogger {
|
|||
return nil
|
||||
}
|
||||
|
||||
return backlog.New(msgr.Channel)
|
||||
return backlogger.New(msgr.Channel)
|
||||
}
|
||||
|
||||
func (msgr *Messenger) AsTypingIndicator() cchat.TypingIndicator {
|
||||
return indicate.NewTyping(msgr.Channel)
|
||||
return indicator.NewTyping(msgr.Channel)
|
||||
}
|
||||
|
||||
func (msgr *Messenger) AsUnreadIndicator() cchat.UnreadIndicator {
|
||||
return indicate.NewUnread(msgr.Channel)
|
||||
return indicator.NewUnread(msgr.Channel)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package messenger
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
)
|
||||
|
||||
type nicknamer struct {
|
||||
userID discord.UserID
|
||||
shared.Channel
|
||||
}
|
||||
|
||||
// New creates a new nicknamer for self.
|
||||
func NewMeNicknamer(ch shared.Channel) cchat.Nicknamer {
|
||||
return NewUserNicknamer(ch.State.UserID, ch)
|
||||
}
|
||||
|
||||
// NewUserNicknamer creates a new nicknamer for the given user ID.
|
||||
func NewUserNicknamer(userID discord.UserID, ch shared.Channel) cchat.Nicknamer {
|
||||
return nicknamer{userID, ch}
|
||||
}
|
||||
|
||||
func (nn nicknamer) Nickname(ctx context.Context, labeler cchat.LabelContainer) (func(), error) {
|
||||
return nn.State.Labels.AddMemberLabel(nn.GuildID, nn.userID, labeler), nil
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
package complete
|
||||
package completer
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
)
|
||||
|
||||
|
@ -40,7 +40,7 @@ func DMChannels(s *state.Instance, word string) []cchat.CompletionEntry {
|
|||
func rankChannel(word string, ch discord.Channel) int {
|
||||
switch ch.Type {
|
||||
case discord.GroupDM, discord.DirectMessage:
|
||||
return rankFunc(word, ch.Name+" "+shared.ChannelName(ch))
|
||||
return rankFunc(word, ch.Name+" "+mention.ChannelName(ch))
|
||||
default:
|
||||
return rankFunc(word, ch.Name)
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
package complete
|
||||
package completer
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package complete
|
||||
package completer
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
|
@ -1,4 +1,4 @@
|
|||
package complete
|
||||
package completer
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
|
@ -51,7 +51,7 @@ func GuildMessageMentions(
|
|||
|
||||
entries = append(entries, cchat.CompletionEntry{
|
||||
Raw: msg.Author.Mention(),
|
||||
Text: message.RenderAuthorName(user),
|
||||
Text: message.RenderUserName(user),
|
||||
Secondary: text.Plain(msg.Author.Username + "#" + msg.Author.Discriminator),
|
||||
IconURL: msg.Author.AvatarURL(),
|
||||
})
|
||||
|
@ -252,7 +252,7 @@ func (ch ChannelCompleter) CompleteMentions(word string) []cchat.CompletionEntry
|
|||
|
||||
entries = append(entries, cchat.CompletionEntry{
|
||||
Raw: raw,
|
||||
Text: message.RenderAuthorName(user),
|
||||
Text: message.RenderUserName(user),
|
||||
Secondary: text.Plain(m.User.Username + "#" + m.User.Discriminator),
|
||||
IconURL: user.Avatar(),
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
package send
|
||||
package sender
|
||||
|
||||
import (
|
||||
"github.com/diamondburned/arikawa/v2/api"
|
||||
|
@ -6,21 +6,22 @@ import (
|
|||
"github.com/diamondburned/arikawa/v2/utils/json/option"
|
||||
"github.com/diamondburned/arikawa/v2/utils/sendpart"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send/complete"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/config"
|
||||
"github.com/diamondburned/cchat-discord/internal/config"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/sender/completer"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
)
|
||||
|
||||
var (
|
||||
allowAllMention = []api.AllowedMentionType{
|
||||
api.AllowEveryoneMention,
|
||||
api.AllowRoleMention,
|
||||
api.AllowUserMention,
|
||||
}
|
||||
)
|
||||
var allowAllMention = []api.AllowedMentionType{
|
||||
api.AllowEveryoneMention,
|
||||
api.AllowRoleMention,
|
||||
api.AllowUserMention,
|
||||
}
|
||||
|
||||
// WrapMessage wraps the given msg to return a new SendMessageData.
|
||||
func WrapMessage(
|
||||
s *state.Instance, ch discord.ChannelID, msg cchat.SendableMessage) api.SendMessageData {
|
||||
|
||||
func WrapMessage(s *state.Instance, msg cchat.SendableMessage) api.SendMessageData {
|
||||
var send = api.SendMessageData{
|
||||
Content: msg.Content(),
|
||||
}
|
||||
|
@ -47,7 +48,8 @@ func WrapMessage(s *state.Instance, msg cchat.SendableMessage) api.SendMessageDa
|
|||
RepliedUser: option.False,
|
||||
}
|
||||
|
||||
if config.MentionOnReply() {
|
||||
repTo, err := s.Cabinet.Message(ch, discord.MessageID(id))
|
||||
if err == nil && config.MentionOnReply(repTo.ID.Time()) {
|
||||
send.AllowedMentions.RepliedUser = option.True
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +68,7 @@ func New(ch shared.Channel) Sender {
|
|||
}
|
||||
|
||||
func (s Sender) Send(msg cchat.SendableMessage) error {
|
||||
_, err := s.State.SendMessageComplex(s.ID, WrapMessage(s.State, msg))
|
||||
_, err := s.State.SendMessageComplex(s.ID, WrapMessage(s.State, s.ID, msg))
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -76,7 +78,7 @@ func (s Sender) CanAttach() bool {
|
|||
}
|
||||
|
||||
func (s Sender) AsCompleter() cchat.Completer {
|
||||
return complete.New(s.Channel)
|
||||
return completer.New(s.Channel)
|
||||
}
|
||||
|
||||
func addAttachments(atts []cchat.MessageAttachment) []sendpart.File {
|
|
@ -7,8 +7,8 @@ import (
|
|||
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/guild"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/shared/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/guild"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
|
@ -6,10 +6,10 @@ import (
|
|||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/category"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/shared/funcutil"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/category"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -45,7 +45,7 @@ func (g *Guild) ID() cchat.ID {
|
|||
}
|
||||
|
||||
func (g *Guild) Name(ctx context.Context, l cchat.LabelContainer) (func(), error) {
|
||||
return g.state.Labels.AddGuildLabel(g.id, l)
|
||||
return g.state.Labels.AddGuildLabel(g.id, l), nil
|
||||
}
|
||||
|
||||
func (g *Guild) AsLister() cchat.Lister { return g }
|
||||
|
@ -53,7 +53,7 @@ func (g *Guild) AsLister() cchat.Lister { return g }
|
|||
func (g *Guild) Servers(container cchat.ServersContainer) (func(), error) {
|
||||
c, err := g.state.Channels(g.id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to get channels")
|
||||
return nil, errors.Wrap(err, "Failed to get channels")
|
||||
}
|
||||
|
||||
// Only get top-level channels (those with category ID being null).
|
||||
|
@ -89,13 +89,11 @@ func (g *Guild) Servers(container cchat.ServersContainer) (func(), error) {
|
|||
|
||||
container.SetServers(chs)
|
||||
|
||||
// TODO: account for insertion/deletion.
|
||||
// TODO: RACEEEEEEEEEEEEEEEEEEEEEEE CONDITION!!!!!!!!!!!!
|
||||
|
||||
// TODO: Add channel stuff.
|
||||
|
||||
stop := funcutil.JoinCancels()
|
||||
|
||||
// TODO: account for insertion/deletion.
|
||||
|
||||
return stop, nil
|
||||
}
|
|
@ -7,8 +7,8 @@ import (
|
|||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send/complete"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/message"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/sender/completer"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state/nonce"
|
||||
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
||||
|
@ -83,21 +83,21 @@ func NewMessages(s *state.Instance, acList *activeList, adder ChannelAdder) *Mes
|
|||
messages: make(messageList, 0, 100),
|
||||
}
|
||||
|
||||
hubServer.sender.completers.Prefixes = complete.CompleterPrefixes{
|
||||
hubServer.sender.completers.Prefixes = completer.CompleterPrefixes{
|
||||
':': func(word string) []cchat.CompletionEntry {
|
||||
return complete.Emojis(s, 0, word)
|
||||
return completer.Emojis(s, 0, word)
|
||||
},
|
||||
'@': func(word string) []cchat.CompletionEntry {
|
||||
if word != "" {
|
||||
return complete.AllUsers(s, word)
|
||||
return completer.AllUsers(s, word)
|
||||
}
|
||||
|
||||
hubServer.msgMutex.Lock()
|
||||
defer hubServer.msgMutex.Unlock()
|
||||
return complete.MessageMentions(hubServer.messages)
|
||||
return completer.MessageMentions(hubServer.messages)
|
||||
},
|
||||
'#': func(word string) []cchat.CompletionEntry {
|
||||
return complete.DMChannels(s, word)
|
||||
return completer.DMChannels(s, word)
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -170,7 +170,7 @@ func (msgs *Messages) JoinServer(ctx context.Context, ct cchat.MessagesContainer
|
|||
user := mention.NewUser(msg.Author)
|
||||
user.WithState(msgs.state.State)
|
||||
|
||||
var author = message.NewAuthor(user)
|
||||
var author = message.NewAuthor(msgs.state, user)
|
||||
if isReply {
|
||||
c, err := msgs.state.Channel(msg.ChannelID)
|
||||
if err == nil {
|
|
@ -6,8 +6,8 @@ import (
|
|||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send/complete"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/sender"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/sender/completer"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state/nonce"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
|
@ -28,7 +28,7 @@ type Sender struct {
|
|||
sentMsgs *nonce.Set
|
||||
state *state.Instance
|
||||
|
||||
completers complete.Completer
|
||||
completers completer.Completer
|
||||
}
|
||||
|
||||
// mentionRegex matche the following:
|
||||
|
@ -89,7 +89,7 @@ func (s *Sender) Send(sendable cchat.SendableMessage) error {
|
|||
s.adder.AddChannel(s.state, channel)
|
||||
}
|
||||
|
||||
sendData := send.WrapMessage(s.state, sendable)
|
||||
sendData := sender.WrapMessage(s.state, channel.ID, sendable)
|
||||
sendData.Content = strings.TrimPrefix(content, matches[0])
|
||||
|
||||
// Store the nonce.
|
|
@ -1,6 +1,7 @@
|
|||
package hub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -137,7 +138,10 @@ func New(s *state.Instance, adder ChannelAdder) (*Server, error) {
|
|||
|
||||
func (hub *Server) ID() cchat.ID { return "!!!hub-server!!!" }
|
||||
|
||||
func (hub *Server) Name() text.Rich { return text.Plain("Incoming Messages") }
|
||||
func (hub *Server) Name(_ context.Context, l cchat.LabelContainer) (func(), error) {
|
||||
l.SetLabel(text.Plain("Incoming Messages"))
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
// ActiveChannelIDs returns the list of active channel IDs, that is, the channel
|
||||
// IDs that should be displayed separately.
|
|
@ -1,14 +1,15 @@
|
|||
package private
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/channel"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/private/hub"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/channel"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/private/hub"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
|
@ -90,8 +91,9 @@ func (priv Private) ID() cchat.ID {
|
|||
return "!!!private-container!!!"
|
||||
}
|
||||
|
||||
func (priv Private) Name() text.Rich {
|
||||
return text.Plain("Private Channels")
|
||||
func (priv Private) Name(_ context.Context, l cchat.LabelContainer) (func(), error) {
|
||||
l.SetLabel(text.Plain("Private Channels"))
|
||||
return func() {}, nil
|
||||
}
|
||||
|
||||
func (priv Private) AsLister() cchat.Lister { return priv }
|
||||
|
@ -115,7 +117,7 @@ func (active activeChannel) LastMessageID() discord.MessageID {
|
|||
return discord.MessageID(active.Channel.ID)
|
||||
}
|
||||
|
||||
func (priv Private) Servers(container cchat.ServersContainer) error {
|
||||
func (priv Private) Servers(container cchat.ServersContainer) (func(), error) {
|
||||
activeIDs := priv.hub.ActiveChannelIDs()
|
||||
|
||||
channels := make([]activeChannel, 0, len(activeIDs))
|
||||
|
@ -123,7 +125,7 @@ func (priv Private) Servers(container cchat.ServersContainer) error {
|
|||
for _, id := range activeIDs {
|
||||
c, err := priv.state.Channel(id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get private channel")
|
||||
return nil, errors.Wrap(err, "failed to get private channel")
|
||||
}
|
||||
|
||||
channels = append(channels, activeChannel{
|
||||
|
@ -144,7 +146,7 @@ func (priv Private) Servers(container cchat.ServersContainer) error {
|
|||
for i, ch := range channels {
|
||||
c, err := channel.New(priv.state, *ch.Channel)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create server for private channel")
|
||||
return nil, errors.Wrap(err, "failed to create server for private channel")
|
||||
}
|
||||
|
||||
servers[i+1] = c
|
||||
|
@ -152,5 +154,5 @@ func (priv Private) Servers(container cchat.ServersContainer) error {
|
|||
|
||||
container.SetServers(servers)
|
||||
priv.containers.Register(container)
|
||||
return nil
|
||||
return func() {}, nil
|
||||
}
|
|
@ -7,13 +7,11 @@ import (
|
|||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/arikawa/v2/session"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/folder"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/guild"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/private"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/shared/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/guild"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/guild/folder"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/session/private"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
"github.com/diamondburned/cchat/text"
|
||||
"github.com/diamondburned/cchat/utils/empty"
|
||||
"github.com/diamondburned/ningen/v2"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -44,27 +42,7 @@ func (s *Session) ID() cchat.ID {
|
|||
}
|
||||
|
||||
func (s *Session) Name(ctx context.Context, l cchat.LabelContainer) (func(), error) {
|
||||
u, err := s.state.Cabinet.Me()
|
||||
if err != nil {
|
||||
l.SetLabel(text.Plain("<@" + s.state.UserID.String() + ">"))
|
||||
} else {
|
||||
user := mention.NewUser(*u)
|
||||
user.WithState(s.state.State)
|
||||
user.Prefetch()
|
||||
|
||||
rich := text.Plain(user.DisplayName())
|
||||
rich.Segments = []text.Segment{
|
||||
mention.Segment{
|
||||
End: len(rich.Content),
|
||||
User: user,
|
||||
},
|
||||
}
|
||||
|
||||
l.SetLabel(rich)
|
||||
}
|
||||
|
||||
// TODO.
|
||||
return func() {}, nil
|
||||
return s.state.Labels.AddPresenceLabel(s.state.UserID, l), nil
|
||||
}
|
||||
|
||||
func (s *Session) Disconnect() error {
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
package nonce
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
cryptorand "crypto/rand"
|
||||
mathrand "math/rand"
|
||||
)
|
||||
|
||||
func init() {
|
||||
mathrand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
var nonceCounter uint64
|
||||
|
||||
// generateNonce generates a unique nonce ID.
|
||||
func generateNonce() string {
|
||||
return fmt.Sprintf(
|
||||
"%s-%s-%s",
|
||||
strconv.FormatInt(time.Now().Unix(), 36),
|
||||
randomBits(),
|
||||
strconv.FormatUint(atomic.AddUint64(&nonceCounter, 1), 36),
|
||||
)
|
||||
}
|
||||
|
||||
// randomBits returns a string 6 bytes long with random characters that are safe
|
||||
// to print. It falls back to math/rand's pseudorandom number generator if it
|
||||
// cannot read from the system entropy pool.
|
||||
func randomBits() string {
|
||||
randBits := make([]byte, 2)
|
||||
|
||||
_, err := cryptorand.Read(randBits)
|
||||
if err != nil {
|
||||
binary.LittleEndian.PutUint32(randBits, mathrand.Uint32())
|
||||
}
|
||||
|
||||
return base64.RawStdEncoding.EncodeToString(randBits)
|
||||
}
|
||||
|
||||
// Map is a nonce state that keeps track of known nonces and generates a
|
||||
// Discord-compatible nonce string.
|
||||
type Map sync.Map
|
||||
|
||||
// Generate generates a new internal nonce, add a bind from the new nonce to the
|
||||
// original nonce, then return the new nonce. If the given original nonce is
|
||||
// empty, then an empty string is returned.
|
||||
func (nmap *Map) Generate(original string) string {
|
||||
// Ignore empty nonces.
|
||||
if original == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
newNonce := generateNonce()
|
||||
(*sync.Map)(nmap).Store(newNonce, original)
|
||||
return newNonce
|
||||
}
|
||||
|
||||
// Load grabs the nonce and permanently deleting it if the given nonce is found.
|
||||
func (nmap *Map) Load(newNonce string) string {
|
||||
v, ok := (*sync.Map)(nmap).LoadAndDelete(newNonce)
|
||||
if ok {
|
||||
return v.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Set is a unique set of nonces.
|
||||
type Set sync.Map
|
||||
|
||||
var nonceSentinel = struct{}{}
|
||||
|
||||
func (nset *Set) Store(nonce string) {
|
||||
(*sync.Map)(nset).Store(nonce, nonceSentinel)
|
||||
}
|
||||
|
||||
func (nset *Set) Has(nonce string) bool {
|
||||
_, ok := (*sync.Map)(nset).Load(nonce)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (nset *Set) HasAndDelete(nonce string) bool {
|
||||
_, ok := (*sync.Map)(nset).LoadAndDelete(nonce)
|
||||
return ok
|
||||
}
|
|
@ -6,16 +6,16 @@ import (
|
|||
)
|
||||
|
||||
type labelContainers struct {
|
||||
guilds map[discord.GuildID]guildContainer
|
||||
channels map[discord.ChannelID]labelerList
|
||||
// presences map[discord.UserID]labelerList
|
||||
guilds map[discord.GuildID]guildContainer
|
||||
channels map[discord.ChannelID]labelerList
|
||||
presences map[discord.UserID]labelerList
|
||||
}
|
||||
|
||||
func newLabelContainers() labelContainers {
|
||||
return labelContainers{
|
||||
guilds: map[discord.GuildID]guildContainer{},
|
||||
channels: map[discord.ChannelID]labelerList{},
|
||||
// presences: map[discord.UserID]labelerList{},
|
||||
guilds: map[discord.GuildID]guildContainer{},
|
||||
channels: map[discord.ChannelID]labelerList{},
|
||||
presences: map[discord.UserID]labelerList{},
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/gateway"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||
"github.com/diamondburned/ningen/v2"
|
||||
)
|
||||
|
@ -17,7 +18,7 @@ import (
|
|||
// adder function will do nothing and will return a callback that does nothing.
|
||||
type Repository struct {
|
||||
state *ningen.State
|
||||
detachs []func()
|
||||
detach func()
|
||||
stopped bool
|
||||
|
||||
mutex sync.Mutex
|
||||
|
@ -31,13 +32,15 @@ func NewRepository(state *ningen.State) *Repository {
|
|||
stores: newLabelContainers(),
|
||||
}
|
||||
|
||||
r.detachs = []func(){
|
||||
r.detach = funcutil.JoinCancels(
|
||||
state.AddHandler(r.onGuildUpdate),
|
||||
state.AddHandler(r.onMemberUpdate),
|
||||
state.AddHandler(r.onMemberRemove),
|
||||
state.AddHandler(r.onChannelUpdate),
|
||||
state.AddHandler(r.onChannelDelete),
|
||||
}
|
||||
state.AddHandler(r.onGuildMembersChunk),
|
||||
// TODO: *gateway.GuildMemberListUpdate
|
||||
)
|
||||
|
||||
return &r
|
||||
}
|
||||
|
@ -115,11 +118,41 @@ func (r *Repository) onMemberUpdate(ev *gateway.GuildMemberUpdateEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
// AddMemberLabel adds a label to display the given member live. Refer to
|
||||
func (r *Repository) onGuildMembersChunk(chunk *gateway.GuildMembersChunkEvent) {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
|
||||
if r.stopped {
|
||||
return
|
||||
}
|
||||
|
||||
guild, ok := r.stores.guilds[chunk.GuildID]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for _, member := range chunk.Members {
|
||||
m, ok := guild.members[member.User.ID]
|
||||
if ok {
|
||||
rich := mention.NewMemberText(r.state, chunk.GuildID, member.User.ID)
|
||||
|
||||
for labeler := range m {
|
||||
labeler.SetLabel(rich)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddMemberLabel adds a label to display the given member live. If the given
|
||||
// guildID is not valid, then AddPresenceLabel will be called. Refer to
|
||||
// Repository for more documentation.
|
||||
func (r *Repository) AddMemberLabel(
|
||||
guildID discord.GuildID, userID discord.UserID, l cchat.LabelContainer) func() {
|
||||
|
||||
if !guildID.IsValid() {
|
||||
return r.AddPresenceLabel(userID, l)
|
||||
}
|
||||
|
||||
l.SetLabel(mention.NewMemberText(r.state, guildID, userID))
|
||||
|
||||
r.mutex.Lock()
|
||||
|
@ -208,14 +241,18 @@ func (r *Repository) AddChannelLabel(chID discord.ChannelID, l cchat.LabelContai
|
|||
}
|
||||
}
|
||||
|
||||
func (r *Repository) AddPresenceLabel(uID discord.UserID, l cchat.LabelContainer) func() {
|
||||
// TODO: Presence update events
|
||||
// TODO: user fallbacks
|
||||
panic("Implement me")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop detaches all handlers.
|
||||
func (r *Repository) Stop() {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
|
||||
r.stopped = true
|
||||
r.mutex.Unlock()
|
||||
|
||||
for _, detach := range r.detachs {
|
||||
detach()
|
||||
}
|
||||
r.detach()
|
||||
}
|
|
@ -5,11 +5,11 @@ import (
|
|||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/diamondburned/arikawa/utils/httputil/httpdriver"
|
||||
"github.com/diamondburned/arikawa/v2/discord"
|
||||
"github.com/diamondburned/arikawa/v2/session"
|
||||
"github.com/diamondburned/arikawa/v2/state"
|
||||
"github.com/diamondburned/arikawa/v2/state/store/defaultstore"
|
||||
"github.com/diamondburned/arikawa/v2/utils/httputil/httpdriver"
|
||||
"github.com/diamondburned/cchat"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state/labels"
|
||||
"github.com/diamondburned/cchat-discord/internal/discord/state/nonce"
|
|
@ -63,14 +63,20 @@ func NewChannelText(s *ningen.State, chID discord.ChannelID) text.Rich {
|
|||
}
|
||||
|
||||
rich := text.Rich{Content: ChannelName(*ch)}
|
||||
rich.Segments = []text.Segment{
|
||||
Segment{
|
||||
Start: 0,
|
||||
End: len(rich.Content),
|
||||
Channel: NewChannel(*ch),
|
||||
},
|
||||
segment := Segment{
|
||||
Start: 0,
|
||||
End: len(rich.Content),
|
||||
}
|
||||
|
||||
if ch.Type == discord.DirectMessage && len(ch.DMRecipients) == 1 {
|
||||
segment.User = NewUser(ch.DMRecipients[0])
|
||||
segment.User.WithState(s)
|
||||
segment.User.Prefetch()
|
||||
} else {
|
||||
segment.Channel = NewChannel(*ch)
|
||||
}
|
||||
|
||||
rich.Segments = []text.Segment{segment}
|
||||
return rich
|
||||
}
|
||||
|
||||
|
|
|
@ -91,6 +91,11 @@ func (um *User) UserID() discord.UserID {
|
|||
return um.user.ID
|
||||
}
|
||||
|
||||
// GuildID returns the guild ID, if any.
|
||||
func (um *User) GuildID() discord.GuildID {
|
||||
return um.guildID
|
||||
}
|
||||
|
||||
// SetGuildID sets the user's guild ID.
|
||||
func (um *User) WithGuildID(guildID discord.GuildID) {
|
||||
um.guildID = guildID
|
||||
|
|
Loading…
Reference in New Issue