class_name KeetFeed
extends Node
## Multi-room message aggregator.
## Polls multiple rooms and emits a unified timeline of messages.
signal feed_updated(messages: Array)
signal new_entry(room_id: String, message: Dictionary)
signal error_occurred(message: String)
@export var client: KeetClient
@export var auto_poll: bool = false
@export_range(1.0, 30.0) var poll_interval: float = 5.0
@export_range(1, 50) var per_room_limit: int = 10
var _watched_rooms: Array[String] = []
var _poll_timer: Timer
var _known_ids: Dictionary = {}
var _timeline: Array = []
func _ready() -> void:
_poll_timer = Timer.new()
_poll_timer.wait_time = poll_interval
_poll_timer.timeout.connect(_poll_all)
add_child(_poll_timer)
if auto_poll and _watched_rooms.size() > 0:
start()
## Add a room to the feed.
func add_room(room_id: String) -> void:
if room_id not in _watched_rooms:
_watched_rooms.append(room_id)
## Remove a room from the feed.
func remove_room(room_id: String) -> void:
_watched_rooms.erase(room_id)
## Set all watched rooms at once.
func set_rooms(room_ids: Array[String]) -> void:
_watched_rooms = room_ids
## Start polling all watched rooms.
func start() -> void:
if _poll_timer.is_stopped():
_poll_timer.start()
_poll_all()
## Stop polling.
func stop() -> void:
_poll_timer.stop()
## Get the current timeline (sorted by timestamp, newest first).
func get_timeline() -> Array:
return _timeline
## Refresh the feed immediately.
func refresh() -> void:
_poll_all()
func _poll_all() -> void:
if client == null or not client.is_connected:
return
for rid: String in _watched_rooms:
_poll_room(rid)
func _poll_room(room_id: String) -> void:
client.rpc("read", {"roomId": room_id, "limit": per_room_limit}, func(result, err):
if err != null:
return
if not result is Array:
return
var has_new := false
for msg: Variant in result:
if not msg is Dictionary:
continue
var mid: String = _msg_id(room_id, msg)
if _known_ids.has(mid):
continue
_known_ids[mid] = true
var entry: Dictionary = {
"room_id": room_id,
"message": msg,
"timestamp": msg.get("timestamp", 0)
}
_timeline.append(entry)
has_new = true
new_entry.emit(room_id, msg)
if has_new:
_timeline.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
return a.get("timestamp", 0) > b.get("timestamp", 0)
)
# Cap timeline size
if _timeline.size() > 200:
_timeline.resize(200)
feed_updated.emit(_timeline)
)
func _msg_id(room_id: String, msg: Dictionary) -> String:
var id: Variant = msg.get("id", null)
if id is Dictionary:
return "%s/%s:%d" % [room_id, id.get("deviceId", ""), id.get("seq", 0)]
if id is String:
return "%s/%s" % [room_id, id]
return "%s/%s:%d" % [room_id, msg.get("memberId", ""), msg.get("seq", 0)]