OverviewFilesCommitsIssuesDocsReleasesPeers

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

183 lines · gd ref:
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.")