forked from 42wim/matterbridge
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for Harmony (42wim#1656)
Harmony is a relatively new (1,5yo) chat protocol with a small community. This introduces support for Harmony into Matterbridge, using the functionality specifically designed for bridge bots. The implementation is a modest 200 lines of code.
- Loading branch information
Showing
31 changed files
with
29,626 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,252 @@ | ||
package harmony | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/42wim/matterbridge/bridge" | ||
"github.com/42wim/matterbridge/bridge/config" | ||
"github.com/harmony-development/shibshib" | ||
chatv1 "github.com/harmony-development/shibshib/gen/chat/v1" | ||
typesv1 "github.com/harmony-development/shibshib/gen/harmonytypes/v1" | ||
profilev1 "github.com/harmony-development/shibshib/gen/profile/v1" | ||
) | ||
|
||
type cachedProfile struct { | ||
data *profilev1.GetProfileResponse | ||
lastUpdated time.Time | ||
} | ||
|
||
type Bharmony struct { | ||
*bridge.Config | ||
|
||
c *shibshib.Client | ||
profileCache map[uint64]cachedProfile | ||
} | ||
|
||
func uToStr(in uint64) string { | ||
return strconv.FormatUint(in, 10) | ||
} | ||
|
||
func strToU(in string) (uint64, error) { | ||
return strconv.ParseUint(in, 10, 64) | ||
} | ||
|
||
func New(cfg *bridge.Config) bridge.Bridger { | ||
b := &Bharmony{ | ||
Config: cfg, | ||
profileCache: map[uint64]cachedProfile{}, | ||
} | ||
|
||
return b | ||
} | ||
|
||
func (b *Bharmony) getProfile(u uint64) (*profilev1.GetProfileResponse, error) { | ||
if v, ok := b.profileCache[u]; ok && time.Since(v.lastUpdated) < time.Minute*10 { | ||
return v.data, nil | ||
} | ||
|
||
resp, err := b.c.ProfileKit.GetProfile(&profilev1.GetProfileRequest{ | ||
UserId: u, | ||
}) | ||
if err != nil { | ||
if v, ok := b.profileCache[u]; ok { | ||
return v.data, nil | ||
} | ||
return nil, err | ||
} | ||
b.profileCache[u] = cachedProfile{ | ||
data: resp, | ||
lastUpdated: time.Now(), | ||
} | ||
return resp, nil | ||
} | ||
|
||
func (b *Bharmony) avatarFor(m *chatv1.Message) string { | ||
if m.Overrides != nil { | ||
return m.Overrides.GetAvatar() | ||
} | ||
|
||
profi, err := b.getProfile(m.AuthorId) | ||
if err != nil { | ||
return "" | ||
} | ||
|
||
return b.c.TransformHMCURL(profi.Profile.GetUserAvatar()) | ||
} | ||
|
||
func (b *Bharmony) usernameFor(m *chatv1.Message) string { | ||
if m.Overrides != nil { | ||
return m.Overrides.GetUsername() | ||
} | ||
|
||
profi, err := b.getProfile(m.AuthorId) | ||
if err != nil { | ||
return "" | ||
} | ||
|
||
return profi.Profile.UserName | ||
} | ||
|
||
func (b *Bharmony) toMessage(msg *shibshib.LocatedMessage) config.Message { | ||
message := config.Message{} | ||
message.Account = b.Account | ||
message.UserID = uToStr(msg.Message.AuthorId) | ||
message.Avatar = b.avatarFor(msg.Message) | ||
message.Username = b.usernameFor(msg.Message) | ||
message.Channel = uToStr(msg.ChannelID) | ||
message.ID = uToStr(msg.MessageId) | ||
|
||
switch content := msg.Message.Content.Content.(type) { | ||
case *chatv1.Content_EmbedMessage: | ||
message.Text = "Embed" | ||
case *chatv1.Content_AttachmentMessage: | ||
var s strings.Builder | ||
for idx, attach := range content.AttachmentMessage.Files { | ||
s.WriteString(b.c.TransformHMCURL(attach.Id)) | ||
if idx < len(content.AttachmentMessage.Files)-1 { | ||
s.WriteString(", ") | ||
} | ||
} | ||
message.Text = s.String() | ||
case *chatv1.Content_PhotoMessage: | ||
var s strings.Builder | ||
for idx, attach := range content.PhotoMessage.GetPhotos() { | ||
s.WriteString(attach.GetCaption().GetText()) | ||
s.WriteString("\n") | ||
s.WriteString(b.c.TransformHMCURL(attach.GetHmc())) | ||
if idx < len(content.PhotoMessage.GetPhotos())-1 { | ||
s.WriteString("\n\n") | ||
} | ||
} | ||
message.Text = s.String() | ||
case *chatv1.Content_TextMessage: | ||
message.Text = content.TextMessage.Content.Text | ||
} | ||
|
||
return message | ||
} | ||
|
||
func (b *Bharmony) outputMessages() { | ||
for { | ||
msg := <-b.c.EventsStream() | ||
|
||
if msg.Message.AuthorId == b.c.UserID { | ||
continue | ||
} | ||
|
||
b.Remote <- b.toMessage(msg) | ||
} | ||
} | ||
|
||
func (b *Bharmony) GetUint64(conf string) uint64 { | ||
num, err := strToU(b.GetString(conf)) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
return num | ||
} | ||
|
||
func (b *Bharmony) Connect() (err error) { | ||
b.c, err = shibshib.NewClient(b.GetString("Homeserver"), b.GetString("Token"), b.GetUint64("UserID")) | ||
if err != nil { | ||
return | ||
} | ||
b.c.SubscribeToGuild(b.GetUint64("Community")) | ||
|
||
go b.outputMessages() | ||
|
||
return nil | ||
} | ||
|
||
func (b *Bharmony) send(msg config.Message) (id string, err error) { | ||
msgChan, err := strToU(msg.Channel) | ||
if err != nil { | ||
return | ||
} | ||
|
||
retID, err := b.c.ChatKit.SendMessage(&chatv1.SendMessageRequest{ | ||
GuildId: b.GetUint64("Community"), | ||
ChannelId: msgChan, | ||
Content: &chatv1.Content{ | ||
Content: &chatv1.Content_TextMessage{ | ||
TextMessage: &chatv1.Content_TextContent{ | ||
Content: &chatv1.FormattedText{ | ||
Text: msg.Text, | ||
}, | ||
}, | ||
}, | ||
}, | ||
Overrides: &chatv1.Overrides{ | ||
Username: &msg.Username, | ||
Avatar: &msg.Avatar, | ||
Reason: &chatv1.Overrides_Bridge{Bridge: &typesv1.Empty{}}, | ||
}, | ||
InReplyTo: nil, | ||
EchoId: nil, | ||
Metadata: nil, | ||
}) | ||
if err != nil { | ||
err = fmt.Errorf("send: error sending message: %w", err) | ||
log.Println(err.Error()) | ||
} | ||
|
||
return uToStr(retID.MessageId), err | ||
} | ||
|
||
func (b *Bharmony) delete(msg config.Message) (id string, err error) { | ||
msgChan, err := strToU(msg.Channel) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
msgID, err := strToU(msg.ID) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
_, err = b.c.ChatKit.DeleteMessage(&chatv1.DeleteMessageRequest{ | ||
GuildId: b.GetUint64("Community"), | ||
ChannelId: msgChan, | ||
MessageId: msgID, | ||
}) | ||
return "", err | ||
} | ||
|
||
func (b *Bharmony) typing(msg config.Message) (id string, err error) { | ||
msgChan, err := strToU(msg.Channel) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
_, err = b.c.ChatKit.Typing(&chatv1.TypingRequest{ | ||
GuildId: b.GetUint64("Community"), | ||
ChannelId: msgChan, | ||
}) | ||
return "", err | ||
} | ||
|
||
func (b *Bharmony) Send(msg config.Message) (id string, err error) { | ||
switch msg.Event { | ||
case "": | ||
return b.send(msg) | ||
case config.EventMsgDelete: | ||
return b.delete(msg) | ||
case config.EventUserTyping: | ||
return b.typing(msg) | ||
default: | ||
return "", nil | ||
} | ||
} | ||
|
||
func (b *Bharmony) JoinChannel(channel config.ChannelInfo) error { | ||
return nil | ||
} | ||
|
||
func (b *Bharmony) Disconnect() error { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//go:build !noharmony | ||
// +build !noharmony | ||
|
||
package bridgemap | ||
|
||
import ( | ||
bharmony "github.com/42wim/matterbridge/bridge/harmony" | ||
) | ||
|
||
func init() { | ||
FullMap["harmony"] = bharmony.New | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.