OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-social / keet-social / addons / keet_social / keet_feed.gd

115 lines · gd ref:
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)]