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
|
package discord
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/diamondburned/cchat"
|
"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/authenticate"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/config"
|
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/session"
|
"github.com/diamondburned/cchat-discord/internal/discord/session"
|
||||||
"github.com/diamondburned/cchat-discord/internal/segments/avatar"
|
"github.com/diamondburned/cchat-discord/internal/segments/avatar"
|
||||||
"github.com/diamondburned/cchat/services"
|
"github.com/diamondburned/cchat/services"
|
||||||
|
@ -29,11 +31,13 @@ type Service struct {
|
||||||
empty.Service
|
empty.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Service) Name() text.Rich {
|
func (Service) Name(_ context.Context, l cchat.LabelContainer) (func(), error) {
|
||||||
return text.Rich{
|
l.SetLabel(text.Rich{
|
||||||
Content: "Discord",
|
Content: "Discord",
|
||||||
Segments: []text.Segment{Logo},
|
Segments: []text.Segment{Logo},
|
||||||
}
|
})
|
||||||
|
|
||||||
|
return func() {}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (Service) Authenticate() []cchat.Authenticator {
|
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"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var World = ®istry{
|
type customType interface {
|
||||||
configs: []config{
|
Marshal() string
|
||||||
{"Mention on Reply", true},
|
Unmarshal(string) error
|
||||||
{"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 config struct {
|
type config struct {
|
||||||
|
@ -55,13 +43,14 @@ func (c *config) Unmarshal(src map[string]string) (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var v interface{}
|
switch v := c.Value.(type) {
|
||||||
|
|
||||||
switch c.Value.(type) {
|
|
||||||
case bool:
|
case bool:
|
||||||
v, err = strconv.ParseBool(strVal)
|
c.Value, err = strconv.ParseBool(strVal)
|
||||||
case string:
|
case string:
|
||||||
v = strVal
|
c.Value = strVal
|
||||||
|
case customType:
|
||||||
|
err = v.Unmarshal(strVal)
|
||||||
|
c.Value = v
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("unknown type %T", c.Value)
|
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
|
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
|
package message
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/cchat"
|
"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/discord/state"
|
||||||
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
||||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||||
|
@ -13,22 +14,26 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Author struct {
|
type Author struct {
|
||||||
name text.Rich
|
name text.Rich
|
||||||
user *mention.User // same pointer as in name
|
user *mention.User // same pointer as in name
|
||||||
|
state *state.Instance
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ cchat.User = (*Author)(nil)
|
var _ cchat.User = (*Author)(nil)
|
||||||
|
|
||||||
// NewAuthor creates a new message author.
|
// 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{
|
return Author{
|
||||||
name: RenderAuthorName(user),
|
name: RenderUserName(user),
|
||||||
user: user,
|
user: user,
|
||||||
|
state: s,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderAuthorName renders the given user mention into a text segment.
|
// RenderUserName renders the given user mention into a text segment.
|
||||||
func RenderAuthorName(user *mention.User) text.Rich {
|
func RenderUserName(user *mention.User) text.Rich {
|
||||||
var rich text.Rich
|
var rich text.Rich
|
||||||
richUser(&rich, user)
|
richUser(&rich, user)
|
||||||
return rich
|
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
|
return
|
||||||
}
|
}
|
||||||
|
@ -55,17 +64,18 @@ func (a Author) ID() cchat.ID {
|
||||||
return a.user.UserID().String()
|
return a.user.UserID().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a Author) Name() text.Rich {
|
// Name subscribes the author to the global name label registry.
|
||||||
return a.name
|
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.state.Labels.AddPresenceLabel(a.user.UserID(), l), nil
|
||||||
return a.user.Avatar()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const authorReplyingTo = " replying to "
|
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.
|
// Specifically, this function is used for direct messages in virtual channels.
|
||||||
func (a *Author) AddUserReply(user discord.User, s *state.Instance) {
|
func (a *Author) AddUserReply(user discord.User, s *state.Instance) {
|
||||||
a.name.Content += authorReplyingTo
|
a.name.Content += authorReplyingTo
|
||||||
|
@ -87,7 +97,7 @@ func (a *Author) AddChannelReply(ch discord.Channel, s *state.Instance) {
|
||||||
}
|
}
|
||||||
|
|
||||||
a.name.Content += authorReplyingTo
|
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,
|
a.name.Segments = append(a.name.Segments,
|
||||||
mention.Segment{
|
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) {
|
func (a *Author) AddMessageReference(ref discord.Message, s *state.Instance) {
|
||||||
a.name.Content += authorReplyingTo
|
a.name.Content += authorReplyingTo
|
||||||
|
|
|
@ -100,7 +100,7 @@ func NewGuildMessageCreate(c *gateway.MessageCreateEvent, s *state.Instance) Mes
|
||||||
|
|
||||||
user.Prefetch()
|
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
|
// 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.WithState(s.State)
|
||||||
user.Prefetch()
|
user.Prefetch()
|
||||||
|
|
||||||
return NewMessage(m, s, NewAuthor(user))
|
return NewMessage(m, s, NewAuthor(s, user))
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDirectMessage creates a new direct message.
|
// NewDirectMessage creates a new direct message.
|
||||||
|
@ -127,7 +127,7 @@ func NewDirectMessage(m discord.Message, s *state.Instance) Message {
|
||||||
user.WithState(s.State)
|
user.WithState(s.State)
|
||||||
user.Prefetch()
|
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.
|
// 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.WithGuildID(msg.GuildID)
|
||||||
user.WithMember(m)
|
user.WithMember(m)
|
||||||
|
|
||||||
author := NewAuthor(user)
|
author := NewAuthor(s, user)
|
||||||
if ref := ReferencedMessage(msg, s, true); ref != nil {
|
if ref := ReferencedMessage(msg, s, true); ref != nil {
|
||||||
author.AddMessageReference(*ref, s)
|
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 {
|
if m.author.user == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -374,7 +374,11 @@ func segmentFuncFromMention(m discord.Message, s *state.Instance) func(i, j int)
|
||||||
|
|
||||||
user.Prefetch()
|
user.Prefetch()
|
||||||
|
|
||||||
return mention.NewSegment(i, j, user)
|
return mention.Segment{
|
||||||
|
Start: i,
|
||||||
|
End: j,
|
||||||
|
User: user,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package category
|
package category
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/cchat"
|
"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-discord/internal/discord/state"
|
||||||
"github.com/diamondburned/cchat/text"
|
|
||||||
"github.com/diamondburned/cchat/utils/empty"
|
"github.com/diamondburned/cchat/utils/empty"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -76,24 +76,16 @@ func (c *Category) ID() cchat.ID {
|
||||||
return c.id.String()
|
return c.id.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Category) Name() text.Rich {
|
func (c *Category) Name(_ context.Context, l cchat.LabelContainer) (func(), error) {
|
||||||
t, err := c.state.Channel(c.id)
|
return c.state.Labels.AddChannelLabel(c.id, l), nil
|
||||||
if err != nil {
|
|
||||||
// This shouldn't happen.
|
|
||||||
return text.Rich{Content: c.id.String()}
|
|
||||||
}
|
|
||||||
|
|
||||||
return text.Rich{
|
|
||||||
Content: t.Name,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Category) AsLister() cchat.Lister { return c }
|
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)
|
t, err := c.state.Channels(c.guildID)
|
||||||
if err != nil {
|
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.
|
// Filter out channels with this category ID.
|
||||||
|
@ -107,12 +99,12 @@ func (c *Category) Servers(container cchat.ServersContainer) error {
|
||||||
for i := range chs {
|
for i := range chs {
|
||||||
c, err := channel.New(c.state, chs[i])
|
c, err := channel.New(c.state, chs[i])
|
||||||
if err != nil {
|
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
|
chv[i] = c
|
||||||
}
|
}
|
||||||
|
|
||||||
container.SetServers(chv)
|
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"
|
"strings"
|
||||||
|
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/commands"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/commands"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send/complete"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/sender/completer"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Commander struct {
|
type Commander struct {
|
||||||
shared.Channel
|
shared.Channel
|
||||||
msgCompl complete.ChannelCompleter
|
msgCompl completer.ChannelCompleter
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommander(ch shared.Channel) cchat.Commander {
|
func NewCommander(ch shared.Channel) cchat.Commander {
|
||||||
return Commander{
|
return Commander{
|
||||||
Channel: ch,
|
Channel: ch,
|
||||||
msgCompl: complete.ChannelCompleter{
|
msgCompl: completer.ChannelCompleter{
|
||||||
Channel: ch,
|
Channel: ch,
|
||||||
},
|
},
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@ package commands
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Command struct {
|
type Command struct {
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/bot/extras/arguments"
|
"github.com/diamondburned/arikawa/v2/bot/extras/arguments"
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"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"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package action
|
package actioner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/cchat"
|
"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"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package backlog
|
package backlogger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/cchat"
|
"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/message"
|
||||||
|
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package edit
|
package editor
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/cchat"
|
"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"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,26 +1,53 @@
|
||||||
package typer
|
package indicator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
"github.com/diamondburned/cchat"
|
"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/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/discord/state"
|
||||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Typer struct {
|
type TypingIndicator struct {
|
||||||
message.Author
|
shared.Channel
|
||||||
time discord.UnixTimestamp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// 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
|
var user *mention.User
|
||||||
|
|
||||||
if ev.GuildID.IsValid() {
|
if ev.GuildID.IsValid() {
|
||||||
|
@ -56,12 +83,5 @@ func New(s *state.Instance, ev *gateway.TypingStartEvent) (*Typer, error) {
|
||||||
user.WithState(s.State)
|
user.WithState(s.State)
|
||||||
user.Prefetch()
|
user.Prefetch()
|
||||||
|
|
||||||
return &Typer{
|
return message.NewAuthor(s, user), nil
|
||||||
Author: message.NewAuthor(user),
|
|
||||||
time: ev.Timestamp,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t Typer) Time() time.Time {
|
|
||||||
return t.time.Time()
|
|
||||||
}
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
package indicate
|
package indicator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/cchat"
|
"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/diamondburned/ningen/v2/states/read"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
|
@ -1,4 +1,4 @@
|
||||||
package memberlist
|
package memberlister
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
"github.com/diamondburned/cchat"
|
"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/emoji"
|
||||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
|
@ -40,18 +40,21 @@ func (l *Member) ID() cchat.ID {
|
||||||
return l.mention.UserID().String()
|
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()
|
l.mention.Prefetch()
|
||||||
content := l.mention.DisplayName()
|
content := l.mention.DisplayName()
|
||||||
|
|
||||||
labeler.SetLabel(text.Rich{
|
labeler.SetLabel(text.Rich{
|
||||||
Content: content,
|
Content: content,
|
||||||
Segments: []text.Segment{
|
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 {
|
func (l *Member) Status() cchat.Status {
|
|
@ -1,11 +1,11 @@
|
||||||
package memberlist
|
package memberlister
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
"github.com/diamondburned/cchat"
|
"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"
|
"github.com/diamondburned/ningen/v2/states/member"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package memberlist
|
package memberlister
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -7,7 +7,7 @@ import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
"github.com/diamondburned/cchat"
|
"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"
|
"github.com/diamondburned/cchat/text"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -61,9 +61,9 @@ func (s Section) ID() cchat.ID {
|
||||||
return s.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))
|
labeler.SetLabel(text.Plain(s.name))
|
||||||
return nil
|
return func() {}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Section) Total() int {
|
func (s Section) Total() int {
|
|
@ -1,4 +1,4 @@
|
||||||
package message
|
package messenger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -7,15 +7,14 @@ import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
"github.com/diamondburned/cchat"
|
"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/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-discord/internal/funcutil"
|
||||||
"github.com/diamondburned/cchat/utils/empty"
|
"github.com/diamondburned/cchat/utils/empty"
|
||||||
)
|
)
|
||||||
|
@ -107,7 +106,7 @@ func (msgr *Messenger) AsSender() cchat.Sender {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return send.New(msgr.Channel)
|
return sender.New(msgr.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msgr *Messenger) AsEditor() cchat.Editor {
|
func (msgr *Messenger) AsEditor() cchat.Editor {
|
||||||
|
@ -115,22 +114,22 @@ func (msgr *Messenger) AsEditor() cchat.Editor {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return edit.New(msgr.Channel)
|
return editor.New(msgr.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msgr *Messenger) AsActioner() cchat.Actioner {
|
func (msgr *Messenger) AsActioner() cchat.Actioner {
|
||||||
return action.New(msgr.Channel)
|
return actioner.New(msgr.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msgr *Messenger) AsNicknamer() cchat.Nicknamer {
|
func (msgr *Messenger) AsNicknamer() cchat.Nicknamer {
|
||||||
return nickname.New(msgr.Channel)
|
return NewMeNicknamer(msgr.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msgr *Messenger) AsMemberLister() cchat.MemberLister {
|
func (msgr *Messenger) AsMemberLister() cchat.MemberLister {
|
||||||
if !msgr.GuildID.IsValid() {
|
if !msgr.GuildID.IsValid() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return memberlist.New(msgr.Channel)
|
return memberlister.New(msgr.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msgr *Messenger) AsBacklogger() cchat.Backlogger {
|
func (msgr *Messenger) AsBacklogger() cchat.Backlogger {
|
||||||
|
@ -138,13 +137,13 @@ func (msgr *Messenger) AsBacklogger() cchat.Backlogger {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return backlog.New(msgr.Channel)
|
return backlogger.New(msgr.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msgr *Messenger) AsTypingIndicator() cchat.TypingIndicator {
|
func (msgr *Messenger) AsTypingIndicator() cchat.TypingIndicator {
|
||||||
return indicate.NewTyping(msgr.Channel)
|
return indicator.NewTyping(msgr.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msgr *Messenger) AsUnreadIndicator() cchat.UnreadIndicator {
|
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 (
|
import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/cchat"
|
"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/discord/state"
|
||||||
|
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||||
"github.com/diamondburned/cchat/text"
|
"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 {
|
func rankChannel(word string, ch discord.Channel) int {
|
||||||
switch ch.Type {
|
switch ch.Type {
|
||||||
case discord.GroupDM, discord.DirectMessage:
|
case discord.GroupDM, discord.DirectMessage:
|
||||||
return rankFunc(word, ch.Name+" "+shared.ChannelName(ch))
|
return rankFunc(word, ch.Name+" "+mention.ChannelName(ch))
|
||||||
default:
|
default:
|
||||||
return rankFunc(word, ch.Name)
|
return rankFunc(word, ch.Name)
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package complete
|
package completer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/diamondburned/cchat"
|
"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"
|
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package complete
|
package completer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
|
@ -1,4 +1,4 @@
|
||||||
package complete
|
package completer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
|
@ -51,7 +51,7 @@ func GuildMessageMentions(
|
||||||
|
|
||||||
entries = append(entries, cchat.CompletionEntry{
|
entries = append(entries, cchat.CompletionEntry{
|
||||||
Raw: msg.Author.Mention(),
|
Raw: msg.Author.Mention(),
|
||||||
Text: message.RenderAuthorName(user),
|
Text: message.RenderUserName(user),
|
||||||
Secondary: text.Plain(msg.Author.Username + "#" + msg.Author.Discriminator),
|
Secondary: text.Plain(msg.Author.Username + "#" + msg.Author.Discriminator),
|
||||||
IconURL: msg.Author.AvatarURL(),
|
IconURL: msg.Author.AvatarURL(),
|
||||||
})
|
})
|
||||||
|
@ -252,7 +252,7 @@ func (ch ChannelCompleter) CompleteMentions(word string) []cchat.CompletionEntry
|
||||||
|
|
||||||
entries = append(entries, cchat.CompletionEntry{
|
entries = append(entries, cchat.CompletionEntry{
|
||||||
Raw: raw,
|
Raw: raw,
|
||||||
Text: message.RenderAuthorName(user),
|
Text: message.RenderUserName(user),
|
||||||
Secondary: text.Plain(m.User.Username + "#" + m.User.Discriminator),
|
Secondary: text.Plain(m.User.Username + "#" + m.User.Discriminator),
|
||||||
IconURL: user.Avatar(),
|
IconURL: user.Avatar(),
|
||||||
})
|
})
|
|
@ -1,4 +1,4 @@
|
||||||
package send
|
package sender
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/diamondburned/arikawa/v2/api"
|
"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/json/option"
|
||||||
"github.com/diamondburned/arikawa/v2/utils/sendpart"
|
"github.com/diamondburned/arikawa/v2/utils/sendpart"
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send/complete"
|
"github.com/diamondburned/cchat-discord/internal/config"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/sender/completer"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/config"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/shared"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var allowAllMention = []api.AllowedMentionType{
|
||||||
allowAllMention = []api.AllowedMentionType{
|
api.AllowEveryoneMention,
|
||||||
api.AllowEveryoneMention,
|
api.AllowRoleMention,
|
||||||
api.AllowRoleMention,
|
api.AllowUserMention,
|
||||||
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{
|
var send = api.SendMessageData{
|
||||||
Content: msg.Content(),
|
Content: msg.Content(),
|
||||||
}
|
}
|
||||||
|
@ -47,7 +48,8 @@ func WrapMessage(s *state.Instance, msg cchat.SendableMessage) api.SendMessageDa
|
||||||
RepliedUser: option.False,
|
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
|
send.AllowedMentions.RepliedUser = option.True
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +68,7 @@ func New(ch shared.Channel) Sender {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Sender) Send(msg cchat.SendableMessage) error {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +78,7 @@ func (s Sender) CanAttach() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Sender) AsCompleter() cchat.Completer {
|
func (s Sender) AsCompleter() cchat.Completer {
|
||||||
return complete.New(s.Channel)
|
return completer.New(s.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addAttachments(atts []cchat.MessageAttachment) []sendpart.File {
|
func addAttachments(atts []cchat.MessageAttachment) []sendpart.File {
|
|
@ -7,8 +7,8 @@ import (
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/guild"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/guild"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/shared/state"
|
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||||
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
"github.com/diamondburned/cchat-discord/internal/segments/colored"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/diamondburned/cchat/utils/empty"
|
"github.com/diamondburned/cchat/utils/empty"
|
|
@ -6,10 +6,10 @@ import (
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/category"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/channel"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/channel"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/category"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/shared/funcutil"
|
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||||
|
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
||||||
"github.com/diamondburned/cchat/utils/empty"
|
"github.com/diamondburned/cchat/utils/empty"
|
||||||
"github.com/pkg/errors"
|
"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) {
|
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 }
|
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) {
|
func (g *Guild) Servers(container cchat.ServersContainer) (func(), error) {
|
||||||
c, err := g.state.Channels(g.id)
|
c, err := g.state.Channels(g.id)
|
||||||
if err != nil {
|
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).
|
// 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)
|
container.SetServers(chs)
|
||||||
|
|
||||||
|
// TODO: account for insertion/deletion.
|
||||||
// TODO: RACEEEEEEEEEEEEEEEEEEEEEEE CONDITION!!!!!!!!!!!!
|
// TODO: RACEEEEEEEEEEEEEEEEEEEEEEE CONDITION!!!!!!!!!!!!
|
||||||
|
|
||||||
// TODO: Add channel stuff.
|
// TODO: Add channel stuff.
|
||||||
|
|
||||||
stop := funcutil.JoinCancels()
|
stop := funcutil.JoinCancels()
|
||||||
|
|
||||||
// TODO: account for insertion/deletion.
|
|
||||||
|
|
||||||
return stop, nil
|
return stop, nil
|
||||||
}
|
}
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
"github.com/diamondburned/cchat"
|
"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/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"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/state/nonce"
|
"github.com/diamondburned/cchat-discord/internal/discord/state/nonce"
|
||||||
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
"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),
|
messages: make(messageList, 0, 100),
|
||||||
}
|
}
|
||||||
|
|
||||||
hubServer.sender.completers.Prefixes = complete.CompleterPrefixes{
|
hubServer.sender.completers.Prefixes = completer.CompleterPrefixes{
|
||||||
':': func(word string) []cchat.CompletionEntry {
|
':': func(word string) []cchat.CompletionEntry {
|
||||||
return complete.Emojis(s, 0, word)
|
return completer.Emojis(s, 0, word)
|
||||||
},
|
},
|
||||||
'@': func(word string) []cchat.CompletionEntry {
|
'@': func(word string) []cchat.CompletionEntry {
|
||||||
if word != "" {
|
if word != "" {
|
||||||
return complete.AllUsers(s, word)
|
return completer.AllUsers(s, word)
|
||||||
}
|
}
|
||||||
|
|
||||||
hubServer.msgMutex.Lock()
|
hubServer.msgMutex.Lock()
|
||||||
defer hubServer.msgMutex.Unlock()
|
defer hubServer.msgMutex.Unlock()
|
||||||
return complete.MessageMentions(hubServer.messages)
|
return completer.MessageMentions(hubServer.messages)
|
||||||
},
|
},
|
||||||
'#': func(word string) []cchat.CompletionEntry {
|
'#': 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 := mention.NewUser(msg.Author)
|
||||||
user.WithState(msgs.state.State)
|
user.WithState(msgs.state.State)
|
||||||
|
|
||||||
var author = message.NewAuthor(user)
|
var author = message.NewAuthor(msgs.state, user)
|
||||||
if isReply {
|
if isReply {
|
||||||
c, err := msgs.state.Channel(msg.ChannelID)
|
c, err := msgs.state.Channel(msg.ChannelID)
|
||||||
if err == nil {
|
if err == nil {
|
|
@ -6,8 +6,8 @@ import (
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/channel/messenger/sender"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send/complete"
|
"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"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/state/nonce"
|
"github.com/diamondburned/cchat-discord/internal/discord/state/nonce"
|
||||||
"github.com/diamondburned/cchat/utils/empty"
|
"github.com/diamondburned/cchat/utils/empty"
|
||||||
|
@ -28,7 +28,7 @@ type Sender struct {
|
||||||
sentMsgs *nonce.Set
|
sentMsgs *nonce.Set
|
||||||
state *state.Instance
|
state *state.Instance
|
||||||
|
|
||||||
completers complete.Completer
|
completers completer.Completer
|
||||||
}
|
}
|
||||||
|
|
||||||
// mentionRegex matche the following:
|
// mentionRegex matche the following:
|
||||||
|
@ -89,7 +89,7 @@ func (s *Sender) Send(sendable cchat.SendableMessage) error {
|
||||||
s.adder.AddChannel(s.state, channel)
|
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])
|
sendData.Content = strings.TrimPrefix(content, matches[0])
|
||||||
|
|
||||||
// Store the nonce.
|
// Store the nonce.
|
|
@ -1,6 +1,7 @@
|
||||||
package hub
|
package hub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"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) 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
|
// ActiveChannelIDs returns the list of active channel IDs, that is, the channel
|
||||||
// IDs that should be displayed separately.
|
// IDs that should be displayed separately.
|
|
@ -1,14 +1,15 @@
|
||||||
package private
|
package private
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
"github.com/diamondburned/cchat"
|
"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/private/hub"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/private/hub"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/diamondburned/cchat/utils/empty"
|
"github.com/diamondburned/cchat/utils/empty"
|
||||||
|
@ -90,8 +91,9 @@ func (priv Private) ID() cchat.ID {
|
||||||
return "!!!private-container!!!"
|
return "!!!private-container!!!"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (priv Private) Name() text.Rich {
|
func (priv Private) Name(_ context.Context, l cchat.LabelContainer) (func(), error) {
|
||||||
return text.Plain("Private Channels")
|
l.SetLabel(text.Plain("Private Channels"))
|
||||||
|
return func() {}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (priv Private) AsLister() cchat.Lister { return priv }
|
func (priv Private) AsLister() cchat.Lister { return priv }
|
||||||
|
@ -115,7 +117,7 @@ func (active activeChannel) LastMessageID() discord.MessageID {
|
||||||
return discord.MessageID(active.Channel.ID)
|
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()
|
activeIDs := priv.hub.ActiveChannelIDs()
|
||||||
|
|
||||||
channels := make([]activeChannel, 0, len(activeIDs))
|
channels := make([]activeChannel, 0, len(activeIDs))
|
||||||
|
@ -123,7 +125,7 @@ func (priv Private) Servers(container cchat.ServersContainer) error {
|
||||||
for _, id := range activeIDs {
|
for _, id := range activeIDs {
|
||||||
c, err := priv.state.Channel(id)
|
c, err := priv.state.Channel(id)
|
||||||
if err != nil {
|
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{
|
channels = append(channels, activeChannel{
|
||||||
|
@ -144,7 +146,7 @@ func (priv Private) Servers(container cchat.ServersContainer) error {
|
||||||
for i, ch := range channels {
|
for i, ch := range channels {
|
||||||
c, err := channel.New(priv.state, *ch.Channel)
|
c, err := channel.New(priv.state, *ch.Channel)
|
||||||
if err != nil {
|
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
|
servers[i+1] = c
|
||||||
|
@ -152,5 +154,5 @@ func (priv Private) Servers(container cchat.ServersContainer) error {
|
||||||
|
|
||||||
container.SetServers(servers)
|
container.SetServers(servers)
|
||||||
priv.containers.Register(container)
|
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/gateway"
|
||||||
"github.com/diamondburned/arikawa/v2/session"
|
"github.com/diamondburned/arikawa/v2/session"
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/folder"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/guild"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/guild"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/guild/folder"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/private"
|
"github.com/diamondburned/cchat-discord/internal/discord/session/private"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/shared/state"
|
"github.com/diamondburned/cchat-discord/internal/discord/state"
|
||||||
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
"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/cchat/utils/empty"
|
||||||
"github.com/diamondburned/ningen/v2"
|
"github.com/diamondburned/ningen/v2"
|
||||||
"github.com/pkg/errors"
|
"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) {
|
func (s *Session) Name(ctx context.Context, l cchat.LabelContainer) (func(), error) {
|
||||||
u, err := s.state.Cabinet.Me()
|
return s.state.Labels.AddPresenceLabel(s.state.UserID, l), nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) Disconnect() error {
|
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 {
|
type labelContainers struct {
|
||||||
guilds map[discord.GuildID]guildContainer
|
guilds map[discord.GuildID]guildContainer
|
||||||
channels map[discord.ChannelID]labelerList
|
channels map[discord.ChannelID]labelerList
|
||||||
// presences map[discord.UserID]labelerList
|
presences map[discord.UserID]labelerList
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLabelContainers() labelContainers {
|
func newLabelContainers() labelContainers {
|
||||||
return labelContainers{
|
return labelContainers{
|
||||||
guilds: map[discord.GuildID]guildContainer{},
|
guilds: map[discord.GuildID]guildContainer{},
|
||||||
channels: map[discord.ChannelID]labelerList{},
|
channels: map[discord.ChannelID]labelerList{},
|
||||||
// presences: map[discord.UserID]labelerList{},
|
presences: map[discord.UserID]labelerList{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/arikawa/v2/gateway"
|
"github.com/diamondburned/arikawa/v2/gateway"
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-discord/internal/funcutil"
|
||||||
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
"github.com/diamondburned/cchat-discord/internal/segments/mention"
|
||||||
"github.com/diamondburned/ningen/v2"
|
"github.com/diamondburned/ningen/v2"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +18,7 @@ import (
|
||||||
// adder function will do nothing and will return a callback that does nothing.
|
// adder function will do nothing and will return a callback that does nothing.
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
state *ningen.State
|
state *ningen.State
|
||||||
detachs []func()
|
detach func()
|
||||||
stopped bool
|
stopped bool
|
||||||
|
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
|
@ -31,13 +32,15 @@ func NewRepository(state *ningen.State) *Repository {
|
||||||
stores: newLabelContainers(),
|
stores: newLabelContainers(),
|
||||||
}
|
}
|
||||||
|
|
||||||
r.detachs = []func(){
|
r.detach = funcutil.JoinCancels(
|
||||||
state.AddHandler(r.onGuildUpdate),
|
state.AddHandler(r.onGuildUpdate),
|
||||||
state.AddHandler(r.onMemberUpdate),
|
state.AddHandler(r.onMemberUpdate),
|
||||||
state.AddHandler(r.onMemberRemove),
|
state.AddHandler(r.onMemberRemove),
|
||||||
state.AddHandler(r.onChannelUpdate),
|
state.AddHandler(r.onChannelUpdate),
|
||||||
state.AddHandler(r.onChannelDelete),
|
state.AddHandler(r.onChannelDelete),
|
||||||
}
|
state.AddHandler(r.onGuildMembersChunk),
|
||||||
|
// TODO: *gateway.GuildMemberListUpdate
|
||||||
|
)
|
||||||
|
|
||||||
return &r
|
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.
|
// Repository for more documentation.
|
||||||
func (r *Repository) AddMemberLabel(
|
func (r *Repository) AddMemberLabel(
|
||||||
guildID discord.GuildID, userID discord.UserID, l cchat.LabelContainer) func() {
|
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))
|
l.SetLabel(mention.NewMemberText(r.state, guildID, userID))
|
||||||
|
|
||||||
r.mutex.Lock()
|
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.
|
// Stop detaches all handlers.
|
||||||
func (r *Repository) Stop() {
|
func (r *Repository) Stop() {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
|
||||||
|
|
||||||
r.stopped = true
|
r.stopped = true
|
||||||
|
r.mutex.Unlock()
|
||||||
|
|
||||||
for _, detach := range r.detachs {
|
r.detach()
|
||||||
detach()
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -5,11 +5,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/diamondburned/arikawa/utils/httputil/httpdriver"
|
|
||||||
"github.com/diamondburned/arikawa/v2/discord"
|
"github.com/diamondburned/arikawa/v2/discord"
|
||||||
"github.com/diamondburned/arikawa/v2/session"
|
"github.com/diamondburned/arikawa/v2/session"
|
||||||
"github.com/diamondburned/arikawa/v2/state"
|
"github.com/diamondburned/arikawa/v2/state"
|
||||||
"github.com/diamondburned/arikawa/v2/state/store/defaultstore"
|
"github.com/diamondburned/arikawa/v2/state/store/defaultstore"
|
||||||
|
"github.com/diamondburned/arikawa/v2/utils/httputil/httpdriver"
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/state/labels"
|
"github.com/diamondburned/cchat-discord/internal/discord/state/labels"
|
||||||
"github.com/diamondburned/cchat-discord/internal/discord/state/nonce"
|
"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 := text.Rich{Content: ChannelName(*ch)}
|
||||||
rich.Segments = []text.Segment{
|
segment := Segment{
|
||||||
Segment{
|
Start: 0,
|
||||||
Start: 0,
|
End: len(rich.Content),
|
||||||
End: len(rich.Content),
|
|
||||||
Channel: NewChannel(*ch),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
return rich
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,11 @@ func (um *User) UserID() discord.UserID {
|
||||||
return um.user.ID
|
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.
|
// SetGuildID sets the user's guild ID.
|
||||||
func (um *User) WithGuildID(guildID discord.GuildID) {
|
func (um *User) WithGuildID(guildID discord.GuildID) {
|
||||||
um.guildID = guildID
|
um.guildID = guildID
|
||||||
|
|
Loading…
Reference in New Issue