Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*.db*
*.log*

/mautrix-line*
/matrix-line*
/start

/docs
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repos:
exclude: "pb\\.go$"
args:
- "-local"
- "github.com/highesttt/mautrix-line-messenger"
- "github.com/highesttt/matrix-line-messenger"
- "-w"
- id: go-vet-mod
- id: go-staticcheck-repo-mod
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN go build -o mautrix-line ./cmd/mautrix-line
RUN go build -o matrix-line ./cmd/matrix-line

FROM ${DOCKER_HUB}/alpine:3.22

Expand All @@ -19,7 +19,7 @@ ENV UID=1337 \

RUN apk add --no-cache ffmpeg su-exec ca-certificates bash jq curl yq-go olm nodejs

COPY --from=builder /build/mautrix-line /usr/bin/mautrix-line
COPY --from=builder /build/matrix-line /usr/bin/matrix-line
COPY ./docker-run.sh /docker-run.sh
RUN chmod +x /docker-run.sh
ENV BRIDGEV2=1
Expand Down
60 changes: 56 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# matrix-line-messenger

[![Go Report Card](https://goreportcard.com/badge/github.com/highesttt/mautrix-line-messenger)](https://goreportcard.com/report/github.com/highesttt/mautrix-line-messenger)
![Languages](https://img.shields.io/github/languages/top/highesttt/mautrix-line-messenger.svg)
[![License](https://img.shields.io/github/license/highesttt/mautrix-line-messenger.svg)](LICENSE)
[![Go Report Card](https://goreportcard.com/badge/github.com/highesttt/matrix-line-messenger)](https://goreportcard.com/report/github.com/highesttt/matrix-line-messenger)
![Languages](https://img.shields.io/github/languages/top/highesttt/matrix-line-messenger.svg)
[![License](https://img.shields.io/github/license/highesttt/matrix-line-messenger.svg)](LICENSE)

A Matrix bridge for LINE Messenger using mautrix-go.\
Based on the [mautrix-twilio](https://github.com/mautrix/twilio) bridge

## Known issues

> [!NOTE]
> Messages sent to the LINE Bot using Beeper Desktop may appear as indefinitely sending.\
> Use Beeper Mobile to send commands to the LINE Bot account after creating the chat with Beeper Desktop.

## Roadmap

- [x] Basic messaging (encrypted text messages)
Expand All @@ -18,8 +24,54 @@ Based on the [mautrix-twilio](https://github.com/mautrix/twilio) bridge
- [x] Read receipts
- [ ] Typing indicators
- [ ] Reaction support
- [ ] Reply support
- [x] Reply support
- [ ] Prefetch chats
- [x] Group chats
- [x] Media messages (images, videos, voice notes, files)
- [x] Sticker support

## How to Use

1. Clone the repository:

```bash
git clone https://github.com/highesttt/matrix-line-messenger.git
cd matrix-line-messenger
```

2. Create a `data` directory for configuration and data storage:

```bash
mkdir data
```

3. Create a configuration file using [bbctl](https://github.com/beeper/bridge-manager):

```bash
bbctl c --type bridgev2 sh-line > config.yaml
```

4. Move the generated `config.yaml` into the `data` directory:

```bash
mv config.yaml data/
```

5. Change `public_address` in `data/config.yaml` to your desired public address

6. Build and run the bridge using Docker (use -d for detached mode):

```bash
docker compose up --build -d
```

To run the bridge without rebuilding, use:

```bash
docker compose up -d
```

## Login

1. Open the Matrix client of your choice and start a chat with `@sh-line:your.matrix.homeserver.domain`. (For local beeper bridges, use `@sh-line:beeper.local`)
2. Send the command `login` and follow the instructions to log in to your LINE account.
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh
MAUTRIX_VERSION=$(cat go.mod | grep 'maunium.net/go/mautrix ' | awk '{ print $2 }' | head -n1)
GO_LDFLAGS="-s -w -X main.Tag=$(git describe --exact-match --tags 2>/dev/null) -X main.Commit=$(git rev-parse HEAD) -X 'main.BuildTime=`date -Iseconds`' -X 'maunium.net/go/mautrix.GoModVersion=$MAUTRIX_VERSION'"
go build -ldflags="$GO_LDFLAGS" ./cmd/mautrix-line "$@"
go build -ldflags="$GO_LDFLAGS" ./cmd/matrix-line "$@"
6 changes: 3 additions & 3 deletions cmd/mautrix-line/main.go → cmd/matrix-line/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import (
"maunium.net/go/mautrix/bridgev2/matrix/mxmain"

"github.com/highesttt/mautrix-line-messenger/pkg/connector"
"github.com/highesttt/matrix-line-messenger/pkg/connector"
)

// Information to find out exactly which commit the bridge was built from.
Expand All @@ -16,9 +16,9 @@ var (

func main() {
m := mxmain.BridgeMain{
Name: "mautrix-line",
Name: "matrix-line",
Description: "A Matrix-LINE bridge",
URL: "https://github.com/highesttt/mautrix-line-messenger",
URL: "https://github.com/highesttt/matrix-line-messenger",
Version: "0.1.0",
Connector: &connector.LineConnector{},
}
Expand Down
16 changes: 8 additions & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
version: '3.8'
version: "3.8"

services:
mautrix-line:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./data:/data
restart: unless-stopped
matrix-line:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./data:/data
restart: unless-stopped
2 changes: 1 addition & 1 deletion docker-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ if [[ -z "$GID" ]]; then
GID="$UID"
fi

BINARY_NAME=/usr/bin/mautrix-line
BINARY_NAME=/usr/bin/matrix-line

function fixperms {
chown -R $UID:$GID /data
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/highesttt/mautrix-line-messenger
module github.com/highesttt/matrix-line-messenger

go 1.24.0

Expand Down
96 changes: 78 additions & 18 deletions pkg/connector/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import (

"github.com/rs/zerolog"

"github.com/highesttt/mautrix-line-messenger/pkg/e2ee"
"github.com/highesttt/mautrix-line-messenger/pkg/line"
"github.com/highesttt/matrix-line-messenger/pkg/e2ee"
"github.com/highesttt/matrix-line-messenger/pkg/line"
)

type LineClient struct {
Expand Down Expand Up @@ -876,6 +876,7 @@ func (lc *LineClient) queueIncomingMessage(msg *line.Message, opType int) {
Data: *msg,
ID: networkid.MessageID(msg.ID),
ConvertMessageFunc: func(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, data line.Message) (*bridgev2.ConvertedMessage, error) {
replyRelatesTo := lc.resolveReplyRelatesTo(ctx, &data)
// Handle Images
if data.ContentType == 1 { // Image
oid := data.ContentMetadata["OID"]
Expand Down Expand Up @@ -936,10 +937,11 @@ func (lc *LineClient) queueIncomingMessage(msg *line.Message, opType int) {
{
Type: event.EventMessage,
Content: &event.MessageEventContent{
MsgType: event.MsgImage,
Body: "image.jpg",
URL: mxc,
File: file,
MsgType: event.MsgImage,
Body: "image.jpg",
URL: mxc,
File: file,
RelatesTo: replyRelatesTo,
},
},
},
Expand Down Expand Up @@ -1081,11 +1083,12 @@ func (lc *LineClient) queueIncomingMessage(msg *line.Message, opType int) {
{
Type: event.EventMessage,
Content: &event.MessageEventContent{
MsgType: event.MsgVideo,
Body: fileName,
URL: mxc,
File: file,
Info: videoInfo,
MsgType: event.MsgVideo,
Body: fileName,
URL: mxc,
File: file,
Info: videoInfo,
RelatesTo: replyRelatesTo,
},
},
},
Expand Down Expand Up @@ -1194,6 +1197,7 @@ func (lc *LineClient) queueIncomingMessage(msg *line.Message, opType int) {
MimeType: mimeType,
Size: len(fileData),
},
RelatesTo: replyRelatesTo,
},
},
},
Expand Down Expand Up @@ -1249,13 +1253,14 @@ func (lc *LineClient) queueIncomingMessage(msg *line.Message, opType int) {
Type: event.EventMessage,
Content: &event.MessageEventContent{
MsgType: event.MsgImage,
Body: stkTxt,
Body: "sticker.png",
URL: mxc,
File: file,
Info: &event.FileInfo{
MimeType: "image/png",
Size: len(stkData),
},
RelatesTo: replyRelatesTo,
},
},
},
Expand All @@ -1271,8 +1276,9 @@ func (lc *LineClient) queueIncomingMessage(msg *line.Message, opType int) {
{
Type: event.EventMessage,
Content: &event.MessageEventContent{
MsgType: event.MsgText,
Body: stkTxt,
MsgType: event.MsgText,
Body: stkTxt,
RelatesTo: replyRelatesTo,
},
},
},
Expand All @@ -1285,8 +1291,9 @@ func (lc *LineClient) queueIncomingMessage(msg *line.Message, opType int) {
{
Type: event.EventMessage,
Content: &event.MessageEventContent{
MsgType: event.MsgText,
Body: unwrappedText,
MsgType: event.MsgText,
Body: unwrappedText,
RelatesTo: replyRelatesTo,
},
},
},
Expand Down Expand Up @@ -1633,6 +1640,38 @@ func (lc *LineClient) ensurePeerKeyForMessage(ctx context.Context, msg *line.Mes
}
}

// resolveReplyRelatesTo looks up the Matrix event ID for a replied-to LINE message.
func (lc *LineClient) resolveReplyRelatesTo(ctx context.Context, data *line.Message) *event.RelatesTo {
if data == nil {
return nil
}

relatedID := data.RelatedMessageID
if relatedID == "" && data.ContentMetadata != nil {
relatedID = data.ContentMetadata["message_relation_server_message_id"]
}

if relatedID == "" {
return nil
}

if data.MessageRelationType != 0 && data.MessageRelationType != 3 {
return nil
}

dbMsg, err := lc.UserLogin.Bridge.DB.Message.GetPartByID(ctx, lc.UserLogin.ID, networkid.MessageID(relatedID), "")
if err != nil {
lc.UserLogin.Bridge.Log.Debug().Err(err).Str("related_msg_id", relatedID).Msg("Failed to lookup reply target")
return nil
}
if dbMsg == nil || dbMsg.MXID == "" {
lc.UserLogin.Bridge.Log.Debug().Str("related_msg_id", relatedID).Msg("No Matrix event found for reply target")
return nil
}

return &event.RelatesTo{InReplyTo: &event.InReplyTo{EventID: dbMsg.MXID}}
}

func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (*bridgev2.MatrixMessageResponse, error) {
if lc.E2EE == nil {
return nil, fmt.Errorf("E2EE not initialized; cannot send")
Expand Down Expand Up @@ -1986,6 +2025,26 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat
Chunks: chunks,
}

var relatedMsg *database.Message

if msg.ReplyTo != nil {
relatedMsg = msg.ReplyTo
} else if msg.Content.RelatesTo != nil && msg.Content.RelatesTo.InReplyTo != nil {
replyToMXID := msg.Content.RelatesTo.InReplyTo.EventID
if replyToMXID != "" {
dbMsg, err := lc.UserLogin.Bridge.DB.Message.GetPartByMXID(ctx, replyToMXID)
if err == nil && dbMsg != nil {
relatedMsg = dbMsg
}
}
}

if relatedMsg != nil && relatedMsg.ID != "" && !strings.HasPrefix(string(relatedMsg.ID), "local-") {
lineMsg.RelatedMessageID = string(relatedMsg.ID)
lineMsg.MessageRelationType = 3
lineMsg.RelatedMessageServiceCode = 1
}

reqSeq := int(now % 1_000_000_000)
lc.reqSeqMu.Lock()
if lc.sentReqSeqs == nil {
Expand All @@ -1994,13 +2053,14 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat
lc.sentReqSeqs[reqSeq] = time.Now()
lc.reqSeqMu.Unlock()

if err := client.SendMessage(int64(reqSeq), lineMsg); err != nil {
sentMsg, err := client.SendMessage(int64(reqSeq), lineMsg)
if err != nil {
return nil, err
}

return &bridgev2.MatrixMessageResponse{
DB: &database.Message{
ID: networkid.MessageID(lineMsg.ID),
ID: networkid.MessageID(sentMsg.ID),
SenderID: makeUserID(string(lc.UserLogin.ID)),
Timestamp: time.UnixMilli(now),
},
Expand Down
Loading