class_name KeetChat
extends Node
## Chat messaging for a single Keet room.
## Send/receive messages with automatic polling for new messages.
signal message_received(message: Dictionary)
signal messages_loaded(messages: Array)
signal message_sent
signal error_occurred(message: String)
@export var client: KeetClient
@export var room_id: String
@export var auto_poll: bool = true
@export_range(0.5, 10.0) var poll_interval: float = 2.0
@export_range(1, 100) var message_limit: int = 50
var _poll_timer: Timer
var _high_seq: int = -1
var _known_ids: Dictionary = {}
func _ready() -> void:
_poll_timer = Timer.new()
_poll_timer.wait_time = poll_interval
_poll_timer.timeout.connect(_poll_messages)
add_child(_poll_timer)
if auto_poll and room_id != "":
start_polling()
## Start watching a room for new messages.
func watch(rid: String) -> void:
room_id = rid
_high_seq = -1
_known_ids.clear()
start_polling()
## Stop watching for messages.
func unwatch() -> void:
stop_polling()
room_id = ""
_high_seq = -1
_known_ids.clear()
## Send a text message to the current room.
func send(text: String, callback: Callable = Callable()) -> void:
if room_id == "":
error_occurred.emit("No room_id set")
return
_require_client()
client.rpc("send", {"roomId": room_id, "text": text}, func(result, err):
if err != null:
error_occurred.emit("send: %s" % str(err))
return
message_sent.emit()
if callback.is_valid():
callback.call(result)
)
## Load message history for the current room.
func load_history(limit: int = 0, callback: Callable = Callable()) -> void:
if room_id == "":
error_occurred.emit("No room_id set")
return
_require_client()
var lim := limit if limit > 0 else message_limit
client.rpc("read", {"roomId": room_id, "limit": lim}, func(result, err):
if err != null:
error_occurred.emit("load_history: %s" % str(err))
return
var msgs: Array = result if result is Array else []
# Track seen messages
for msg: Variant in msgs:
if msg is Dictionary:
var mid: String = _msg_id(msg)
_known_ids[mid] = true
var seq: int = msg.get("seq", -1) as int
if seq > _high_seq:
_high_seq = seq
messages_loaded.emit(msgs)
if callback.is_valid():
callback.call(msgs)
)
## Edit a message.
func edit(message_id: Variant, text: String, callback: Callable = Callable()) -> void:
_require_client()
client.rpc("msg.edit", {"roomId": room_id, "messageId": message_id, "text": text}, func(result, err):
if err != null:
error_occurred.emit("edit: %s" % str(err))
return
if callback.is_valid():
callback.call(result)
)
## Delete a message.
func delete(message_id: Variant, callback: Callable = Callable()) -> void:
_require_client()
client.rpc("msg.delete", {"roomId": room_id, "messageId": message_id}, func(result, err):
if err != null:
error_occurred.emit("delete: %s" % str(err))
return
if callback.is_valid():
callback.call(result)
)
## React to a message.
func react(message_id: Variant, emoji: String, callback: Callable = Callable()) -> void:
_require_client()
client.rpc("react", {"roomId": room_id, "messageId": message_id, "emoji": emoji}, func(result, err):
if err != null:
error_occurred.emit("react: %s" % str(err))
return
if callback.is_valid():
callback.call(result)
)
## Search messages in the current room.
func search(query: String, callback: Callable = Callable()) -> void:
_require_client()
client.rpc("msg.search", {"roomId": room_id, "query": query}, func(result, err):
if err != null:
error_occurred.emit("search: %s" % str(err))
return
if callback.is_valid():
callback.call(result)
)
func start_polling() -> void:
if not _poll_timer.is_stopped():
return
_poll_timer.start()
# Immediate first poll
_poll_messages()
func stop_polling() -> void:
_poll_timer.stop()
func _poll_messages() -> void:
if room_id == "" or client == null or not client.is_connected:
return
client.rpc("read", {"roomId": room_id, "limit": message_limit}, func(result, err):
if err != null:
return
if not result is Array:
return
for msg: Variant in result:
if not msg is Dictionary:
continue
var mid: String = _msg_id(msg)
if _known_ids.has(mid):
continue
_known_ids[mid] = true
var seq: int = msg.get("seq", -1) as int
if seq > _high_seq:
_high_seq = seq
message_received.emit(msg)
)
func _msg_id(msg: Dictionary) -> String:
var id: Variant = msg.get("id", null)
if id is Dictionary:
return "%s:%d" % [id.get("deviceId", ""), id.get("seq", 0)]
if id is String:
return id
return "%s:%d" % [msg.get("memberId", ""), msg.get("seq", 0)]
func _require_client() -> void:
if client == null:
push_error("KeetChat: client not set. Assign a KeetClient node.")