Implement Poke functionality, refine talking status and add local log events

This commit is contained in:
Jose Luis Montañes Ojados
2026-01-17 00:53:50 +01:00
parent be5e26486c
commit 3a57f41fc2
5 changed files with 125 additions and 20 deletions

View File

@@ -183,6 +183,16 @@ type clientLeftMsg struct {
clientID uint16 clientID uint16
} }
type clientMovedMsg struct {
clientID uint16
channelID uint64
}
type pokeMsg struct {
senderName string
message string
}
type chatMsg struct { type chatMsg struct {
senderID uint16 senderID uint16
senderName string senderName string
@@ -240,6 +250,20 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.audioPlayer.PlayPCM(e.SenderID, e.PCM) m.audioPlayer.PlayPCM(e.SenderID, e.PCM)
} }
}) })
m.client.On(ts3client.EventClientMoved, func(e *ts3client.ClientMovedEvent) {
m.program.Send(clientMovedMsg{
clientID: e.ClientID,
channelID: e.ChannelID,
})
})
m.client.On(ts3client.EventPoke, func(e *ts3client.PokeEvent) {
m.program.Send(pokeMsg{
senderName: e.SenderName,
message: e.Message,
})
})
} }
// Initialize audio player // Initialize audio player
@@ -349,6 +373,29 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
return m, nil return m, nil
case clientMovedMsg:
if msg.clientID == m.selfID {
chName := "Unknown"
if ch := m.client.GetChannel(msg.channelID); ch != nil {
chName = ch.Name
}
m.chatMessages = append(m.chatMessages, ChatMessage{
Time: time.Now(),
Sender: "SYSTEM",
Content: fmt.Sprintf("You moved to channel: %s", chName),
})
}
return m, nil
case pokeMsg:
m.chatMessages = append(m.chatMessages, ChatMessage{
Time: time.Now(),
Sender: "POKE",
Content: fmt.Sprintf("[%s]: %s", msg.senderName, msg.message),
})
m.addLog("Received poke from %s: %s", msg.senderName, msg.message)
return m, nil
case chatMsg: case chatMsg:
m.chatMessages = append(m.chatMessages, ChatMessage{ m.chatMessages = append(m.chatMessages, ChatMessage{
Time: time.Now(), Time: time.Now(),
@@ -569,7 +616,7 @@ func (m *Model) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
func (m *Model) handleChatKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) { func (m *Model) handleChatKeys(_ tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }

View File

@@ -134,26 +134,41 @@ func (c *Client) Connect(address string) error {
defer ticker.Stop() defer ticker.Stop()
for { for {
// Recovery from panics in the main loop
func() {
defer func() {
if r := recover(); r != nil {
log.Printf("PANIC in Client loop: %v", r)
}
}()
select {
case <-c.done:
log.Println("Client loop stopped")
return
case pkt := <-pktChan:
if pkt == nil {
// Channel closed
return
}
if err := c.handlePacket(pkt); err != nil {
log.Printf("Error handling packet: %v", err)
}
case <-ticker.C:
if !c.Connected {
return // Don't send pings if not connected yet
}
// Send KeepAlive Ping (Encrypted, No NewProtocol)
if err := c.sendPing(); err != nil {
log.Printf("Error sending Ping: %v", err)
}
}
}()
// Check if we should exit after the inner function
select { select {
case <-c.done: case <-c.done:
log.Println("Client loop stopped")
return nil return nil
case pkt := <-pktChan: default:
if pkt == nil {
// Channel closed
return nil
}
if err := c.handlePacket(pkt); err != nil {
log.Printf("Error handling packet: %v", err)
}
case <-ticker.C:
if !c.Connected {
continue // Don't send pings if not connected yet
}
// Send KeepAlive Ping (Encrypted, No NewProtocol)
if err := c.sendPing(); err != nil {
log.Printf("Error sending Ping: %v", err)
}
} }
} }
} }

View File

@@ -226,8 +226,8 @@ func (c *Client) processCommand(data []byte, pkt *protocol.Packet) error {
cmdStr := string(data) cmdStr := string(data)
// Debug: Log packet flags and raw command preview (sanitized) // Debug: Log packet flags and raw command preview (sanitized)
log.Printf("Debug Packet: Compressed=%v, Fragmented=%v, RawLen=%d, Preview=%q", log.Printf("Debug Packet: PID=%d, Compressed=%v, Fragmented=%v, RawLen=%d, Preview=%q",
pkt.Header.FlagCompressed(), pkt.Header.FlagFragmented(), len(data), pkt.Header.PacketID, pkt.Header.FlagCompressed(), pkt.Header.FlagFragmented(), len(data),
func() string { func() string {
preview := cmdStr preview := cmdStr
if len(preview) > 100 { if len(preview) > 100 {
@@ -544,6 +544,28 @@ func (c *Client) processCommand(data []byte, pkt *protocol.Packet) error {
"message": msg, "message": msg,
}) })
case "notifyclientpoke":
msg := ""
invoker := "Unknown"
var invokerID uint16
if m, ok := args["msg"]; ok {
msg = protocol.Unescape(m)
}
if name, ok := args["invokername"]; ok {
invoker = protocol.Unescape(name)
}
if iid, ok := args["invokerid"]; ok {
var id uint64
fmt.Sscanf(iid, "%d", &id)
invokerID = uint16(id)
}
log.Printf("[Poke] %s: %s", invoker, msg)
c.emitEvent("client_poke", map[string]any{
"senderID": invokerID,
"senderName": invoker,
"message": msg,
})
case "notifyservergrouplist", "notifychannelgrouplist", "notifyclientneededpermissions": case "notifyservergrouplist", "notifychannelgrouplist", "notifyclientneededpermissions":
// Ignore verbose noisy setup commands // Ignore verbose noisy setup commands
default: default:

View File

@@ -124,6 +124,10 @@ func (c *Client) emit(event EventType, data any) {
if fn, ok := h.(func(*TalkingStatusEvent)); ok { if fn, ok := h.(func(*TalkingStatusEvent)); ok {
fn(data.(*TalkingStatusEvent)) fn(data.(*TalkingStatusEvent))
} }
case EventPoke:
if fn, ok := h.(func(*PokeEvent)); ok {
fn(data.(*PokeEvent))
}
} }
} }
} }
@@ -405,6 +409,13 @@ func (c *Client) handleInternalEvent(eventType string, data map[string]any) {
Channels: getInt(data, "channels"), Channels: getInt(data, "channels"),
}) })
case "client_poke":
c.emit(EventPoke, &PokeEvent{
SenderID: getUint16(data, "senderID"),
SenderName: getString(data, "senderName"),
Message: getString(data, "message"),
})
case "error": case "error":
c.emit(EventError, &ErrorEvent{ c.emit(EventError, &ErrorEvent{
ID: getString(data, "id"), ID: getString(data, "id"),

View File

@@ -25,8 +25,18 @@ const (
// Error events // Error events
EventError EventType = "error" EventError EventType = "error"
// Poke events
EventPoke EventType = "poke"
) )
// PokeEvent is emitted when a poke message is received
type PokeEvent struct {
SenderID uint16
SenderName string
Message string
}
// ConnectedEvent is emitted when the client successfully connects // ConnectedEvent is emitted when the client successfully connects
type ConnectedEvent struct { type ConnectedEvent struct {
ClientID uint16 ClientID uint16