0
0
mirror of https://github.com/thegeeklab/wp-matrix.git synced 2024-11-23 07:00:40 +00:00

refactor: use dedicated matrix package (#111)

This commit is contained in:
Robert Kaussow 2024-05-11 10:25:15 +02:00 committed by GitHub
parent 117f681b10
commit 3c3fb393b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 155 additions and 132 deletions

View File

@ -1,6 +1,6 @@
---
all: True
dir: mocks
dir: "{{.PackageName}}/mocks"
outpkg: "mocks"
packages:
github.com/thegeeklab/wp-matrix/plugin:
github.com/thegeeklab/wp-matrix/matrix:

View File

@ -11,13 +11,14 @@ IMPORT := github.com/thegeeklab/$(EXECUTABLE)
GO ?= go
CWD ?= $(shell pwd)
PACKAGES ?= $(shell go list ./... | grep -Ev 'mocks')
PACKAGES ?= $(shell go list ./... | grep -Ev '/mocks$$')
SOURCES ?= $(shell find . -name "*.go" -type f)
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@$(GOFUMPT_PACKAGE_VERSION)
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_PACKAGE_VERSION)
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GOTESTSUM_PACKAGE ?= gotest.tools/gotestsum@latest
MOCKERY_PACKAGE ?= github.com/vektra/mockery/v2@latest
XGO_VERSION := go-1.22.x
XGO_TARGETS ?= linux/amd64,linux/arm-6,linux/arm-7,linux/arm64
@ -65,6 +66,7 @@ lint: golangci-lint
.PHONY: generate
generate:
$(GO) generate $(PACKAGES)
$(GO) run $(MOCKERY_PACKAGE)
.PHONY: test
test:

14
matrix/api.go Normal file
View File

@ -0,0 +1,14 @@
package matrix
import (
"context"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
//nolint:lll
type APIClient interface {
SendMessageEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, contentJSON interface{}, extra ...mautrix.ReqSendEvent) (resp *mautrix.RespSendEvent, err error)
}

89
matrix/matrix.go Normal file
View File

@ -0,0 +1,89 @@
package matrix
import (
"context"
"fmt"
"github.com/microcosm-cc/bluemonday"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format"
"maunium.net/go/mautrix/id"
)
type Client struct {
client APIClient
Message *Message
}
type Message struct {
client APIClient
Opt MessageOptions
}
type MessageOptions struct {
RoomID id.RoomID
Message string
TemplateUnsafe bool
}
// NewClient creates a new Matrix client with the given parameters and joins the specified room.
// It authenticates the user if the userID and token are not provided, and returns a Client struct
// that can be used to send messages to the room.
func NewClient(ctx context.Context, url, roomID, userID, token, username, password string) (*Client, error) {
muid := id.NewUserID(EnsurePrefix("@", userID), url)
c, err := mautrix.NewClient(url, muid, token)
if err != nil {
return nil, err
}
if userID == "" || token == "" {
_, err := c.Login(
ctx,
&mautrix.ReqLogin{
Type: "m.login.password",
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: username},
Password: password,
InitialDeviceDisplayName: "Woodpecker CI",
StoreCredentials: true,
},
)
if err != nil {
return nil, fmt.Errorf("failed to authenticate user: %w", err)
}
}
joinResp, err := c.JoinRoom(ctx, EnsurePrefix("!", roomID), "", nil)
if err != nil {
return nil, fmt.Errorf("failed to join room: %w", err)
}
return &Client{
client: c,
Message: &Message{
client: c,
Opt: MessageOptions{
RoomID: joinResp.RoomID,
},
},
}, nil
}
// Send sends a message to the specified room. It sanitizes the message content
// to remove potentially unsafe HTML.
func (m *Message) Send(ctx context.Context) error {
content := format.RenderMarkdown(m.Opt.Message, true, m.Opt.TemplateUnsafe)
if content.FormattedBody != "" {
content.Body = format.HTMLToMarkdown(bluemonday.UGCPolicy().Sanitize(content.FormattedBody))
content.FormattedBody = bluemonday.UGCPolicy().Sanitize(content.FormattedBody)
}
_, err := m.client.SendMessageEvent(ctx, m.Opt.RoomID, event.EventMessage, content)
if err != nil {
return err
}
return nil
}

View File

@ -1,4 +1,4 @@
package plugin
package matrix
import (
"context"
@ -6,21 +6,21 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/thegeeklab/wp-matrix/plugin/mocks"
"github.com/thegeeklab/wp-matrix/matrix/mocks"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
)
func TestMatrixMessageSend(t *testing.T) {
func TestMessageSend(t *testing.T) {
tests := []struct {
name string
messageOpt MatrixMessageOpt
messageOpt MessageOptions
want event.MessageEventContent
wantErr bool
}{
{
name: "plain text message",
messageOpt: MatrixMessageOpt{
messageOpt: MessageOptions{
RoomID: "test-room",
Message: "hello world",
},
@ -31,7 +31,7 @@ func TestMatrixMessageSend(t *testing.T) {
},
{
name: "markdown message",
messageOpt: MatrixMessageOpt{
messageOpt: MessageOptions{
RoomID: "test-room",
Message: "**hello world**",
},
@ -44,7 +44,7 @@ func TestMatrixMessageSend(t *testing.T) {
},
{
name: "html message",
messageOpt: MatrixMessageOpt{
messageOpt: MessageOptions{
RoomID: "test-room",
Message: "hello<br>world",
TemplateUnsafe: true,
@ -58,7 +58,7 @@ func TestMatrixMessageSend(t *testing.T) {
},
{
name: "safe html message",
messageOpt: MatrixMessageOpt{
messageOpt: MessageOptions{
RoomID: "test-room",
Message: "hello world<script>alert('XSS')</script>",
TemplateUnsafe: false,
@ -72,7 +72,7 @@ func TestMatrixMessageSend(t *testing.T) {
},
{
name: "unsafe html message",
messageOpt: MatrixMessageOpt{
messageOpt: MessageOptions{
RoomID: "test-room",
Message: "hello world<script>alert('XSS')</script>",
TemplateUnsafe: true,
@ -89,8 +89,8 @@ func TestMatrixMessageSend(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
mockClient := mocks.NewMockMautrixClient(t)
m := &MatrixMessage{
mockClient := mocks.NewMockAPIClient(t)
m := &Message{
Opt: tt.messageOpt,
client: mockClient,
}

View File

@ -1,4 +1,4 @@
// Code generated by mockery v2.42.1. DO NOT EDIT.
// Code generated by mockery v2.43.0. DO NOT EDIT.
package mocks
@ -13,21 +13,21 @@ import (
mock "github.com/stretchr/testify/mock"
)
// MockMautrixClient is an autogenerated mock type for the MautrixClient type
type MockMautrixClient struct {
// MockAPIClient is an autogenerated mock type for the APIClient type
type MockAPIClient struct {
mock.Mock
}
type MockMautrixClient_Expecter struct {
type MockAPIClient_Expecter struct {
mock *mock.Mock
}
func (_m *MockMautrixClient) EXPECT() *MockMautrixClient_Expecter {
return &MockMautrixClient_Expecter{mock: &_m.Mock}
func (_m *MockAPIClient) EXPECT() *MockAPIClient_Expecter {
return &MockAPIClient_Expecter{mock: &_m.Mock}
}
// SendMessageEvent provides a mock function with given fields: ctx, roomID, eventType, contentJSON, extra
func (_m *MockMautrixClient) SendMessageEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, contentJSON interface{}, extra ...mautrix.ReqSendEvent) (*mautrix.RespSendEvent, error) {
func (_m *MockAPIClient) SendMessageEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, contentJSON interface{}, extra ...mautrix.ReqSendEvent) (*mautrix.RespSendEvent, error) {
_va := make([]interface{}, len(extra))
for _i := range extra {
_va[_i] = extra[_i]
@ -63,8 +63,8 @@ func (_m *MockMautrixClient) SendMessageEvent(ctx context.Context, roomID id.Roo
return r0, r1
}
// MockMautrixClient_SendMessageEvent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMessageEvent'
type MockMautrixClient_SendMessageEvent_Call struct {
// MockAPIClient_SendMessageEvent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SendMessageEvent'
type MockAPIClient_SendMessageEvent_Call struct {
*mock.Call
}
@ -74,12 +74,12 @@ type MockMautrixClient_SendMessageEvent_Call struct {
// - eventType event.Type
// - contentJSON interface{}
// - extra ...mautrix.ReqSendEvent
func (_e *MockMautrixClient_Expecter) SendMessageEvent(ctx interface{}, roomID interface{}, eventType interface{}, contentJSON interface{}, extra ...interface{}) *MockMautrixClient_SendMessageEvent_Call {
return &MockMautrixClient_SendMessageEvent_Call{Call: _e.mock.On("SendMessageEvent",
func (_e *MockAPIClient_Expecter) SendMessageEvent(ctx interface{}, roomID interface{}, eventType interface{}, contentJSON interface{}, extra ...interface{}) *MockAPIClient_SendMessageEvent_Call {
return &MockAPIClient_SendMessageEvent_Call{Call: _e.mock.On("SendMessageEvent",
append([]interface{}{ctx, roomID, eventType, contentJSON}, extra...)...)}
}
func (_c *MockMautrixClient_SendMessageEvent_Call) Run(run func(ctx context.Context, roomID id.RoomID, eventType event.Type, contentJSON interface{}, extra ...mautrix.ReqSendEvent)) *MockMautrixClient_SendMessageEvent_Call {
func (_c *MockAPIClient_SendMessageEvent_Call) Run(run func(ctx context.Context, roomID id.RoomID, eventType event.Type, contentJSON interface{}, extra ...mautrix.ReqSendEvent)) *MockAPIClient_SendMessageEvent_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]mautrix.ReqSendEvent, len(args)-4)
for i, a := range args[4:] {
@ -92,23 +92,23 @@ func (_c *MockMautrixClient_SendMessageEvent_Call) Run(run func(ctx context.Cont
return _c
}
func (_c *MockMautrixClient_SendMessageEvent_Call) Return(resp *mautrix.RespSendEvent, err error) *MockMautrixClient_SendMessageEvent_Call {
func (_c *MockAPIClient_SendMessageEvent_Call) Return(resp *mautrix.RespSendEvent, err error) *MockAPIClient_SendMessageEvent_Call {
_c.Call.Return(resp, err)
return _c
}
func (_c *MockMautrixClient_SendMessageEvent_Call) RunAndReturn(run func(context.Context, id.RoomID, event.Type, interface{}, ...mautrix.ReqSendEvent) (*mautrix.RespSendEvent, error)) *MockMautrixClient_SendMessageEvent_Call {
func (_c *MockAPIClient_SendMessageEvent_Call) RunAndReturn(run func(context.Context, id.RoomID, event.Type, interface{}, ...mautrix.ReqSendEvent) (*mautrix.RespSendEvent, error)) *MockAPIClient_SendMessageEvent_Call {
_c.Call.Return(run)
return _c
}
// NewMockMautrixClient creates a new instance of MockMautrixClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// NewMockAPIClient creates a new instance of MockAPIClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockMautrixClient(t interface {
func NewMockAPIClient(t interface {
mock.TestingT
Cleanup(func())
}) *MockMautrixClient {
mock := &MockMautrixClient{}
}) *MockAPIClient {
mock := &MockAPIClient{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })

View File

@ -1,4 +1,4 @@
package plugin
package matrix
import "strings"

View File

@ -1,4 +1,4 @@
package plugin
package matrix
import "testing"

View File

@ -12,9 +12,8 @@ import (
"fmt"
"github.com/rs/zerolog/log"
"github.com/thegeeklab/wp-matrix/matrix"
"github.com/thegeeklab/wp-plugin-go/v2/template"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/id"
)
var ErrAuthSourceNotSet = errors.New("either username and password or userid and accesstoken are required")
@ -44,44 +43,26 @@ func (p *Plugin) Validate() error {
// Execute provides the implementation of the plugin.
func (p *Plugin) Execute() error {
muid := id.NewUserID(EnsurePrefix("@", p.Settings.UserID), p.Settings.Homeserver)
matrix, err := mautrix.NewClient(p.Settings.Homeserver, muid, p.Settings.AccessToken)
if err != nil {
return fmt.Errorf("failed to initialize client: %w", err)
}
if p.Settings.UserID == "" || p.Settings.AccessToken == "" {
_, err := matrix.Login(
p.Network.Context,
&mautrix.ReqLogin{
Type: "m.login.password",
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: p.Settings.Username},
Password: p.Settings.Password,
InitialDeviceDisplayName: "Woodpecker CI",
StoreCredentials: true,
},
)
if err != nil {
return fmt.Errorf("failed to authenticate user: %w", err)
}
}
log.Info().Msg("logged in successfully")
joinResp, err := matrix.JoinRoom(p.Network.Context, EnsurePrefix("!", p.Settings.RoomID), "", nil)
if err != nil {
return fmt.Errorf("failed to join room: %w", err)
}
msg, err := p.CreateMessage()
if err != nil {
return fmt.Errorf("failed to create message: %w", err)
}
client := NewMatrixClient(matrix)
client.Message.Opt = MatrixMessageOpt{
RoomID: joinResp.RoomID,
client, err := matrix.NewClient(
p.Network.Context,
p.Settings.Homeserver,
p.Settings.RoomID,
p.Settings.UserID,
p.Settings.AccessToken,
p.Settings.Username,
p.Settings.Password,
)
if err != nil {
return fmt.Errorf("failed to initialize client: %w", err)
}
client.Message.Opt = matrix.MessageOptions{
RoomID: client.Message.Opt.RoomID,
Message: msg,
TemplateUnsafe: p.Settings.TemplateUnsafe,
}

View File

@ -1,62 +0,0 @@
package plugin
import (
"context"
"fmt"
"github.com/microcosm-cc/bluemonday"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format"
"maunium.net/go/mautrix/id"
)
//nolint:lll
type MautrixClient interface {
SendMessageEvent(ctx context.Context, roomID id.RoomID, eventType event.Type, contentJSON interface{}, extra ...mautrix.ReqSendEvent) (resp *mautrix.RespSendEvent, err error)
}
type MatrixClient struct {
client MautrixClient
Message *MatrixMessage
}
type MatrixMessage struct {
client MautrixClient
Opt MatrixMessageOpt
}
type MatrixMessageOpt struct {
RoomID id.RoomID
Message string
TemplateUnsafe bool
}
// NewMatrixClient creates a new MatrixClient instance with the provided mautrix.Client.
func NewMatrixClient(client *mautrix.Client) *MatrixClient {
return &MatrixClient{
client: client,
Message: &MatrixMessage{
client: client,
Opt: MatrixMessageOpt{},
},
}
}
// Send sends a message to the specified room. It sanitizes the message content
// to remove potentially unsafe HTML.
func (m *MatrixMessage) Send(ctx context.Context) error {
content := format.RenderMarkdown(m.Opt.Message, true, m.Opt.TemplateUnsafe)
if content.FormattedBody != "" {
content.Body = format.HTMLToMarkdown(bluemonday.UGCPolicy().Sanitize(content.FormattedBody))
content.FormattedBody = bluemonday.UGCPolicy().Sanitize(content.FormattedBody)
}
_, err := m.client.SendMessageEvent(ctx, m.Opt.RoomID, event.EventMessage, content)
if err != nil {
return fmt.Errorf("failed to send message: %w", err)
}
return nil
}

View File

@ -13,7 +13,6 @@ import (
"github.com/urfave/cli/v2"
)
//go:generate mockery
//go:generate go run ../internal/doc/main.go -output=../docs/data/data-raw.yaml
//nolint:lll