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
52 changes: 49 additions & 3 deletions pkg/connector/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,22 @@ func (lc *LineClient) handleOperation(ctx context.Context, op line.Operation) {
})
}

if op.Type == 64 || op.Type == 65 {
chatMid := op.Param1
msgID := op.Param2
lc.UserLogin.Bridge.Log.Info().Str("msg_id", msgID).Str("chat_mid", chatMid).Int("op_type", op.Type).Msg("Received unsend operation")

ts, _ := op.CreatedTime.Int64()
lc.UserLogin.Bridge.QueueRemoteEvent(lc.UserLogin, &simplevent.MessageRemove{
EventMeta: simplevent.EventMeta{
Type: bridgev2.RemoteEventMessageRemove,
PortalKey: networkid.PortalKey{ID: makePortalID(chatMid), Receiver: lc.UserLogin.ID},
Timestamp: time.UnixMilli(ts),
},
TargetMessage: networkid.MessageID(msgID),
})
}

if op.Type == 140 {
go func() {
param2, err := line.ParseReactionParam2(op.Param2)
Expand Down Expand Up @@ -1506,9 +1522,11 @@ func (lc *LineClient) LogoutRemote(ctx context.Context) {}

func (lc *LineClient) GetCapabilities(ctx context.Context, portal *bridgev2.Portal) *event.RoomFeatures {
return &event.RoomFeatures{
MaxTextLength: 5000,
Reply: event.CapLevelFullySupported,
ReadReceipts: true,
MaxTextLength: 5000,
Reply: event.CapLevelFullySupported,
ReadReceipts: true,
Delete: event.CapLevelPartialSupport,
DeleteChatForEveryone: true,
File: event.FileFeatureMap{
event.MsgImage: {
MimeTypes: map[string]event.CapabilitySupportLevel{
Expand Down Expand Up @@ -2244,6 +2262,34 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat
}, nil
}

func (lc *LineClient) HandleMatrixMessageRemove(ctx context.Context, msg *bridgev2.MatrixMessageRemove) error {
client := line.NewClient(lc.AccessToken)

reqSeq := int(time.Now().UnixMilli() % 1_000_000_000)
lc.reqSeqMu.Lock()
if lc.sentReqSeqs == nil {
lc.sentReqSeqs = make(map[int]time.Time)
}
lc.sentReqSeqs[reqSeq] = time.Now()
lc.reqSeqMu.Unlock()
Comment on lines +2269 to +2274

Copilot AI Jan 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HandleMatrixMessageRemove stores the generated reqSeq in lc.sentReqSeqs, but there is no corresponding cleanup path for these entries (the only cleanup happens for operations with op.Type == 25), so every Matrix-initiated delete will leak an entry in this map for the lifetime of the process. Either avoid adding reqSeq for operations that don't get an op 25 echo, or extend the cleanup logic to remove entries for the operation types used by unsendMessage so the map doesn't grow without bound.

Suggested change
lc.reqSeqMu.Lock()
if lc.sentReqSeqs == nil {
lc.sentReqSeqs = make(map[int]time.Time)
}
lc.sentReqSeqs[reqSeq] = time.Now()
lc.reqSeqMu.Unlock()

Copilot uses AI. Check for mistakes.

return client.UnsendMessage(int64(reqSeq), string(msg.TargetMessage.ID))
}

func (lc *LineClient) HandleMatrixLeaveRoom(ctx context.Context, portal *bridgev2.Portal) error {
client := line.NewClient(lc.AccessToken)

reqSeq := int(time.Now().UnixMilli() % 1_000_000_000)
lc.reqSeqMu.Lock()
if lc.sentReqSeqs == nil {
lc.sentReqSeqs = make(map[int]time.Time)
}
lc.sentReqSeqs[reqSeq] = time.Now()
lc.reqSeqMu.Unlock()
Comment on lines +2283 to +2288

Copilot AI Jan 21, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly to HandleMatrixMessageRemove, HandleMatrixLeaveRoom records reqSeq values in lc.sentReqSeqs but there is no cleanup path associated with the sendChatRemoved call, so every leave will add another long-lived entry to the map. Consider either not tracking reqSeq for this call at all, or updating the operation handling logic to clear these entries when the corresponding LINE operation is received, to avoid unbounded growth.

Suggested change
lc.reqSeqMu.Lock()
if lc.sentReqSeqs == nil {
lc.sentReqSeqs = make(map[int]time.Time)
}
lc.sentReqSeqs[reqSeq] = time.Now()
lc.reqSeqMu.Unlock()

Copilot uses AI. Check for mistakes.

return client.SendChatRemoved(int64(reqSeq), string(portal.ID), "0", 0)
}

var _ bridgev2.IdentifierResolvingNetworkAPI = (*LineClient)(nil)

func (lc *LineClient) ResolveIdentifier(ctx context.Context, identifier string, createChat bool) (*bridgev2.ResolveIdentifierResponse, error) {
Expand Down
10 changes: 10 additions & 0 deletions pkg/line/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,13 @@ func (c *Client) GetRecentMessagesV2(chatMid string, limit int) ([]*Message, err
}
return wrapper.Data, nil
}

func (c *Client) UnsendMessage(reqSeq int64, messageID string) error {
_, err := c.callRPC("TalkService", "unsendMessage", reqSeq, messageID)
return err
}

func (c *Client) SendChatRemoved(reqSeq int64, chatMid, lastReadMessageId string, lastReadMessageTime int64) error {
_, err := c.callRPC("TalkService", "sendChatRemoved", reqSeq, chatMid, lastReadMessageId, lastReadMessageTime)
return err
}
Loading