From dbedc994216fa2e932f1aefd3ea27832419b85ef Mon Sep 17 00:00:00 2001 From: Janet Blackquill Date: Sat, 18 Dec 2021 16:43:29 -0500 Subject: Add support for Harmony (#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. --- .../harmony-development/shibshib/client.go | 216 +++++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 vendor/github.com/harmony-development/shibshib/client.go (limited to 'vendor/github.com/harmony-development/shibshib/client.go') diff --git a/vendor/github.com/harmony-development/shibshib/client.go b/vendor/github.com/harmony-development/shibshib/client.go new file mode 100644 index 00000000..e0690ad1 --- /dev/null +++ b/vendor/github.com/harmony-development/shibshib/client.go @@ -0,0 +1,216 @@ +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) +} -- cgit v1.2.3