mirror of
https://github.com/cwinfo/matterbridge.git
synced 2024-11-10 11:00:27 +00:00
dbedc99421
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.
217 lines
4.7 KiB
Go
217 lines
4.7 KiB
Go
package shibshib
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
authv1 "github.com/harmony-development/shibshib/gen/auth/v1"
|
|
chatv1 "github.com/harmony-development/shibshib/gen/chat/v1"
|
|
profilev1 "github.com/harmony-development/shibshib/gen/profile/v1"
|
|
)
|
|
|
|
type Client struct {
|
|
ChatKit chatv1.HTTPChatServiceClient
|
|
AuthKit authv1.HTTPAuthServiceClient
|
|
ProfileKit profilev1.HTTPProfileServiceClient
|
|
|
|
ErrorHandler func(error)
|
|
|
|
UserID uint64
|
|
|
|
incomingEvents <-chan *chatv1.StreamEventsResponse
|
|
outgoingEvents chan<- *chatv1.StreamEventsRequest
|
|
|
|
subscribedGuilds []uint64
|
|
onceHandlers []func(*LocatedMessage)
|
|
|
|
events chan *LocatedMessage
|
|
homeserver string
|
|
sessionToken string
|
|
|
|
streaming bool
|
|
|
|
mtx *sync.Mutex
|
|
}
|
|
|
|
var ErrEndOfStream = errors.New("end of stream")
|
|
|
|
func (c *Client) init(h string, wsp, wsph string) {
|
|
c.events = make(chan *LocatedMessage)
|
|
c.mtx = new(sync.Mutex)
|
|
c.ErrorHandler = func(e error) {
|
|
panic(e)
|
|
}
|
|
c.homeserver = h
|
|
c.ChatKit = chatv1.HTTPChatServiceClient{*http.DefaultClient, h, wsp, wsph, http.Header{}}
|
|
c.AuthKit = authv1.HTTPAuthServiceClient{*http.DefaultClient, h, wsp, wsph, http.Header{}}
|
|
c.ProfileKit = profilev1.HTTPProfileServiceClient{*http.DefaultClient, h, wsp, wsph, http.Header{}}
|
|
}
|
|
|
|
func (c *Client) authed(token string, userID uint64) {
|
|
c.sessionToken = token
|
|
c.ChatKit.Header.Add("Authorization", token)
|
|
c.AuthKit.Header.Add("Authorization", token)
|
|
c.ProfileKit.Header.Add("Authorization", token)
|
|
c.UserID = userID
|
|
}
|
|
|
|
func NewClient(homeserver, token string, userid uint64) (ret *Client, err error) {
|
|
url, err := url.Parse(homeserver)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
it := "wss"
|
|
if url.Scheme == "http" {
|
|
it = "ws"
|
|
}
|
|
ret = &Client{}
|
|
ret.homeserver = homeserver
|
|
ret.init(homeserver, it, url.Host)
|
|
ret.authed(token, userid)
|
|
|
|
err = ret.StreamEvents()
|
|
if err != nil {
|
|
ret = nil
|
|
return
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (c *Client) StreamEvents() (err error) {
|
|
c.mtx.Lock()
|
|
defer c.mtx.Unlock()
|
|
|
|
if c.streaming {
|
|
return
|
|
}
|
|
|
|
it := make(chan *chatv1.StreamEventsRequest)
|
|
c.outgoingEvents = it
|
|
c.incomingEvents, err = c.ChatKit.StreamEvents(it)
|
|
if err != nil {
|
|
err = fmt.Errorf("StreamEvents: failed to open stream: %w", err)
|
|
return
|
|
}
|
|
|
|
c.streaming = true
|
|
|
|
go func() {
|
|
for ev := range c.incomingEvents {
|
|
chat, ok := ev.Event.(*chatv1.StreamEventsResponse_Chat)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
msg, ok := chat.Chat.Event.(*chatv1.StreamEvent_SentMessage)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
imsg := &LocatedMessage{
|
|
GuildID: msg.SentMessage.GuildId,
|
|
ChannelID: msg.SentMessage.ChannelId,
|
|
MessageWithId: chatv1.MessageWithId{
|
|
MessageId: msg.SentMessage.MessageId,
|
|
Message: msg.SentMessage.Message,
|
|
},
|
|
}
|
|
|
|
for _, h := range c.onceHandlers {
|
|
h(imsg)
|
|
}
|
|
c.onceHandlers = make([]func(*LocatedMessage), 0)
|
|
c.events <- imsg
|
|
}
|
|
|
|
c.mtx.Lock()
|
|
defer c.mtx.Unlock()
|
|
|
|
c.streaming = false
|
|
c.ErrorHandler(ErrEndOfStream)
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) SubscribeToGuild(community uint64) {
|
|
for _, g := range c.subscribedGuilds {
|
|
if g == community {
|
|
return
|
|
}
|
|
}
|
|
c.outgoingEvents <- &chatv1.StreamEventsRequest{
|
|
Request: &chatv1.StreamEventsRequest_SubscribeToGuild_{
|
|
SubscribeToGuild: &chatv1.StreamEventsRequest_SubscribeToGuild{
|
|
GuildId: community,
|
|
},
|
|
},
|
|
}
|
|
c.subscribedGuilds = append(c.subscribedGuilds, community)
|
|
}
|
|
|
|
func (c *Client) SubscribedGuilds() []uint64 {
|
|
return c.subscribedGuilds
|
|
}
|
|
|
|
func (c *Client) SendMessage(msg *chatv1.SendMessageRequest) (*chatv1.SendMessageResponse, error) {
|
|
return c.ChatKit.SendMessage(msg)
|
|
}
|
|
|
|
func (c *Client) TransformHMCURL(hmc string) string {
|
|
if !strings.HasPrefix(hmc, "hmc://") {
|
|
return fmt.Sprintf("%s/_harmony/media/download/%s", c.homeserver, hmc)
|
|
}
|
|
|
|
trimmed := strings.TrimPrefix(hmc, "hmc://")
|
|
split := strings.Split(trimmed, "/")
|
|
if len(split) != 2 {
|
|
return fmt.Sprintf("malformed URL: %s", hmc)
|
|
}
|
|
|
|
return fmt.Sprintf("https://%s/_harmony/media/download/%s", split[0], split[1])
|
|
}
|
|
|
|
func (c *Client) UsernameFor(m *chatv1.Message) string {
|
|
if m.Overrides != nil {
|
|
return m.Overrides.GetUsername()
|
|
}
|
|
|
|
resp, err := c.ProfileKit.GetProfile(&profilev1.GetProfileRequest{
|
|
UserId: m.AuthorId,
|
|
})
|
|
if err != nil {
|
|
return strconv.FormatUint(m.AuthorId, 10)
|
|
}
|
|
|
|
return resp.Profile.UserName
|
|
}
|
|
|
|
func (c *Client) AvatarFor(m *chatv1.Message) string {
|
|
if m.Overrides != nil {
|
|
return m.Overrides.GetAvatar()
|
|
}
|
|
|
|
resp, err := c.ProfileKit.GetProfile(&profilev1.GetProfileRequest{
|
|
UserId: m.AuthorId,
|
|
})
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
return c.TransformHMCURL(resp.Profile.GetUserAvatar())
|
|
}
|
|
|
|
func (c *Client) EventsStream() <-chan *LocatedMessage {
|
|
return c.events
|
|
}
|
|
|
|
func (c *Client) HandleOnce(f func(*LocatedMessage)) {
|
|
c.onceHandlers = append(c.onceHandlers, f)
|
|
}
|