Du betrachtest gerade Matrix Fotoweiterleitung und Standort BUGFIX

Matrix Fotoweiterleitung und Standort BUGFIX

Matrix LINE-Bridge: Die Matrix Fotoweiterleitung und Standort funktioniert nicht. Hier sind die BUGFIXES die du brauchst.

Der Foto Weiterleitung Fehler

Das Problem: Ich betreibe einen eigenen Matrix-Server (Synapse in Docker) mit einer LINE-Bridge. Textnachrichten, Audio und Video lassen sich aus Element nach LINE weiterleiten, Fotos nicht. Stattdessen erscheint vom Bridge-Bot die Meldung: „Your message may not have been bridged: panic in event handler“.

Video: Matrix Fotoweiterleitung und Standort BUGFIX

Sprache: 🇩🇪|🇬🇧
☝️ Benutze YouTube Untertitel für alle Sprachen.

Die Spurensuche

Im Bridge-Log /matrix/line-bridge/data/logs/bridge.log zeigt sich bei jedem fehlgeschlagenen Foto derselbe Fehler:

runtime error: invalid memory address or nil pointer dereference
connector.(*LineClient).HandleMatrixMessage → send_message.go:189

Dort greift der Code auf msg.Content.Info.MimeType zu, doch Info ist leer (nil). Daraus ergaben sich zwei Erkenntnisse:

  1. Die Wurzel liegt in der Gegenrichtung. Der Handler für eingehende LINE-Fotos (/matrix/line-bridge/matrix-line-messenger/pkg/connector/handlers/image.go) erzeugt ein m.image-Event ohne info-Block, also ohne MIME-Typ, Größe, Breite und Höhe. Audio und Video bekommen diesen Block mit, deshalb funktionieren sie.
  2. Weiterleiten kopiert den Inhalt 1:1. Element übernimmt das Event unverändert. Beim Senden zurück nach LINE fehlt dann info, und die Bridge stürzt ab.

Die Zeitzone von Docker spielt dabei keine Rolle. Die Logs laufen in UTC, 02:20 UTC entsprechen 9:20 in Bangkok.

Der Fix

Geändert werden zwei Dateien:

  • /matrix/line-bridge/matrix-line-messenger/pkg/connector/send_message.go
  • /matrix/line-bridge/matrix-line-messenger/pkg/connector/handlers/image.go

Schritt 0: Sicherung

cd /matrix/line-bridge
mkdir -p backup-vor-image-info-fix/handlers
cp matrix-line-messenger/pkg/connector/send_message.go backup-vor-image-info-fix/
cp matrix-line-messenger/pkg/connector/handlers/image.go backup-vor-image-info-fix/handlers/

Datei 1: /matrix/line-bridge/matrix-line-messenger/pkg/connector/send_message.go

1a) Import ergänzen in /matrix/line-bridge/matrix-line-messenger/pkg/connector/send_message.go (zwischen "image/jpeg" und "regexp"):

	"net/http"

1b) Absturzstelle in /matrix/line-bridge/matrix-line-messenger/pkg/connector/send_message.go (ca. Zeile 189, unter case event.MsgImage:). Ersetze

		mimeType := msg.Content.Info.MimeType

durch

		mimeType := ""
		if msg.Content.Info != nil {
			mimeType = msg.Content.Info.MimeType
		}
		if mimeType == "" {
			mimeType = http.DetectContentType(data)
		}

1c) Optional: Video absichern in /matrix/line-bridge/matrix-line-messenger/pkg/connector/send_message.go(ca. Zeile 401, unter case event.MsgVideo:). Ersetze

		if msg.Content.Info.Duration > 0 {

durch

		if msg.Content.Info != nil && msg.Content.Info.Duration > 0 {

Die Zeile mit Info != nil im Audio-Zweig (ca. Zeile 546) bleibt unverändert.

Datei 2: /matrix/line-bridge/matrix-line-messenger/pkg/connector/handlers/image.go

2a) Import-Block ersetzen in /matrix/line-bridge/matrix-line-messenger/pkg/connector/handlers/image.go durch:

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"image"
	_ "image/jpeg"
	"time"

	"maunium.net/go/mautrix/bridgev2"
	"maunium.net/go/mautrix/event"

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

2b) Info-Block anlegen in /matrix/line-bridge/matrix-line-messenger/pkg/connector/handlers/image.go. Am Ende von ConvertImage, direkt vor dem letzten return &bridgev2.ConvertedMessage{, einfügen:

	imgInfo := &event.FileInfo{
		MimeType: "image/jpeg",
		Size:     len(imgData),
	}
	if cfg, _, cfgErr := image.DecodeConfig(bytes.NewReader(imgData)); cfgErr == nil {
		imgInfo.Width = cfg.Width
		imgInfo.Height = cfg.Height
	}

2c) Info ins Event eintragen in /matrix/line-bridge/matrix-line-messenger/pkg/connector/handlers/image.go. Im selben return die Zeile Info: imgInfo, ergänzen:

					MsgType:   event.MsgImage,
					Body:      "image.jpg",
					URL:       mxc,
					File:      file,
					Info:      imgInfo,
					RelatesTo: relatesTo,

Neu bauen und starten

cd /matrix
docker compose build line-bridge
docker compose up -d line-bridge

Test

Ein altes LINE-Foto weiterleiten. Das fängt Änderung 1b ab. Danach ein neu eingehendes Foto weiterleiten. Im Log darf kein Treffer mehr erscheinen:

docker logs matrix-line-bridge 2>&1 | grep -i "panic\|nil pointer"

Zurückrollen

cd /matrix/line-bridge
cp backup-vor-image-info-fix/send_message.go matrix-line-messenger/pkg/connector/send_message.go
cp backup-vor-image-info-fix/handlers/image.go matrix-line-messenger/pkg/connector/handlers/image.go
cd /matrix && docker compose build line-bridge && docker compose up -d line-bridge

Fazit

„Panic in event handler“ klingt nach einem Infrastrukturproblem, war hier aber ein fehlender nil-Check plus ein unvollständiges Event. Die Log-Zeile mit der Stack-Trace führt direkt zur Codezeile, daher lohnt es sich, bei Bridge-Fehlern zuerst dort nachzusehen.


Standort teilen zwischen Element und LINE (Matrix-Bridge)

Das Problem: Ein in LINE geteilter Standort erscheint in Element nur als „[Location unavailable]“. Ein in Element geteilter Standort („Mein Standort teilen“) kommt bei LINE gar nicht an.

Die Ursachen:

  • LINE schickt den Standort verschlüsselt und schreibt die Koordinaten als Text ("12.96…"). Die Bridge erwartete Zahlen.
  • Die Bridge hatte für das Senden von Standorten keinen Code und lehnte sie mit „m.location not implemented“ ab.

Alle Pfade beginnen bei /matrix. Den gemeinsamen Ordner nenne ich <BRIDGE>:

<BRIDGE> = /matrix/line-bridge/matrix-line-messenger/pkg/connector

Es werden drei Dateien geändert. Öffne sie mit einem Texteditor und achte darauf, dass du Einrückungen (Tabs) beibehältst.

Datei 1: /matrix/line-bridge/matrix-line-messenger/pkg/connector/handle_message.go

Suche (ca. Zeile 435):

		return h.ConvertLocation(data, replyRelatesTo)

Ersetze durch:

		return h.ConvertLocation(data, decryptedBody, replyRelatesTo)

Datei 2: /matrix/line-bridge/matrix-line-messenger/pkg/connector/handlers/location.go

Schritt 1: Ersetze oben den import-Anfang

import (
	"fmt"
	"strings"

durch:

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

Schritt 2: Suche diese Zeilen:

// ConvertLocation converts a LINE location message to a Matrix location message.
func (h *Handler) ConvertLocation(data line.Message, relatesTo *event.RelatesTo) (*bridgev2.ConvertedMessage, error) {
	if data.Location == nil {

Ersetze sie durch diesen Block:

// flexFloat accepts JSON numbers and strings ("12.96"); LINE sends coordinates
// as strings inside the decrypted E2EE payload.
type flexFloat float64

func (f *flexFloat) UnmarshalJSON(b []byte) error {
	s := strings.Trim(string(b), `"`)
	if s == "" || s == "null" {
		return nil
	}
	v, err := strconv.ParseFloat(s, 64)
	if err != nil {
		return err
	}
	*f = flexFloat(v)
	return nil
}

// extractLocation reads the location from the plain field or the decrypted payload.
func extractLocation(data line.Message, decryptedBody string) *line.MessageLocation {
	if data.Location != nil {
		return data.Location
	}
	body := strings.TrimSpace(decryptedBody)
	if !strings.HasPrefix(body, "{") {
		return nil
	}
	var w struct {
		Location *struct {
			Title     string    `json:"title"`
			Address   string    `json:"address"`
			Latitude  flexFloat `json:"latitude"`
			Longitude flexFloat `json:"longitude"`
		} `json:"location"`
	}
	if json.Unmarshal([]byte(body), &w) != nil || w.Location == nil {
		return nil
	}
	l := w.Location
	return &line.MessageLocation{Title: l.Title, Address: l.Address,
		Latitude: float64(l.Latitude), Longitude: float64(l.Longitude)}
}

// ConvertLocation converts a LINE location message to a Matrix location message.
func (h *Handler) ConvertLocation(data line.Message, decryptedBody string, relatesTo *event.RelatesTo) (*bridgev2.ConvertedMessage, error) {
	loc := extractLocation(data, decryptedBody)
	if loc == nil {
		h.Log.Warn().Int("content_type", data.ContentType).Msg("LINE location without usable coordinates")

Schritt 3: Ersetze überall in dieser Datei data.Location. durch loc. (das betrifft Latitude, Longitude, Title und Address, etwa 6 Stellen). Die Zeilen data.Location != nil und return data.Location aus Schritt 2 bleiben unverändert.

Datei 3: /matrix/line-bridge/matrix-line-messenger/pkg/connector/send_message.go

Schritt 1: Suche

	if useE2EEMedia && effectiveMsgType != event.MsgText {

Ersetze durch:

	if useE2EEMedia && effectiveMsgType != event.MsgText && effectiveMsgType != event.MsgLocation {

Schritt 2: Suche die Zeile var plainTextBody string … und füge direkt darunter ein:

	var locationData *line.MessageLocation // set for m.location messages

Schritt 3: Suche am Ende des switch effectiveMsgType { die Zeile default: (darunter steht message type %s not implemented). Füge direkt davor ein:

	case event.MsgLocation:
		lat, lon, ok := parseGeoURI(msg.Content.GeoURI)
		if !ok {
			return nil, fmt.Errorf("invalid geo URI in m.location: %q", msg.Content.GeoURI)
		}
		address := "Location"
		first := strings.TrimSpace(strings.SplitN(msg.Content.Body, "\n", 2)[0])
		if first != "" && len(first) <= 100 && !strings.Contains(first, "geo:") {
			address = first
		}
		locationData = &line.MessageLocation{Title: address, Address: address, Latitude: lat, Longitude: lon}
		contentType = int(ContentLocation)
		if !plainText {
			// Same shape LINE uses for received locations: coordinates as strings.
			payload, err = json.Marshal(map[string]any{"location": map[string]string{
				"address":   address,
				"latitude":  strconv.FormatFloat(lat, 'f', -1, 64),
				"longitude": strconv.FormatFloat(lon, 'f', -1, 64),
			}})
			if err != nil {
				return nil, fmt.Errorf("failed to marshal location payload: %w", err)
			}
		}

Schritt 4: Suche

		HasContent:      contentType != int(ContentText),

Ersetze durch:

		HasContent:      contentType != int(ContentText) && contentType != int(ContentLocation),

Schritt 5: Suche

	if plainText {
		lineMsg.Text = plainTextBody
	} else {

Ersetze durch:

	if plainText {
		lineMsg.Text = plainTextBody
		if locationData != nil {
			lineMsg.Location = locationData
		}
	} else {

Schritt 6: Suche die Funktion contentTypeForMsgType und füge direkt unter switch msgType { diese zwei Zeilen ein:

	case event.MsgLocation:
		return int(ContentLocation)

Schritt 7: Füge ganz am Dateiende diese neue Funktion an:

// parseGeoURI parses "geo:13.7563,100.5018;u=20" into latitude and longitude.
func parseGeoURI(uri string) (lat, lon float64, ok bool) {
	s := strings.TrimPrefix(strings.TrimSpace(uri), "geo:")
	s = strings.SplitN(s, ";", 2)[0]
	parts := strings.Split(s, ",")
	if len(parts) < 2 {
		return 0, 0, false
	}
	var err1, err2 error
	lat, err1 = strconv.ParseFloat(strings.TrimSpace(parts[0]), 64)
	lon, err2 = strconv.ParseFloat(strings.TrimSpace(parts[1]), 64)
	return lat, lon, err1 == nil && err2 == nil
}

Neu bauen und testen

  1. Im Terminal: cd /matrix
  2. docker compose build line-bridge && docker compose up -d line-bridge
  3. Test 1: In LINE einen Standort senden. In Element erscheint jetzt eine Karte mit Adresse.
  4. Test 2: In Element „Mein Standort teilen“ wählen. In LINE kommt der Standort an.

Beide Richtungen habe ich so getestet, und sie funktionieren.

Hinweis zum Live-Standort: Das „Live-Standort teilen“ von Element ist damit nicht abgedeckt. Element verwendet dafür andere Ereignisse, die die Bridge nicht weitergibt. Statische Standorte funktionieren in beide Richtungen.


Spenden Bild

Link zur Unterstützung / Spende für den Kanal
Wenn meine Beiträge hilfreich sind oder dir geholfen haben, würde ich mich über eine Unterstützung sehr freuen 🙏

PayPal Link
Überweisung, Bitcoin und Lightning


#Matrix #Selfhosting #Docker #LINE #Synapse #Element

Schreibe einen Kommentar