refactor: enhance test framework with automated resource tracking and scripted error capture capabilities
This commit is contained in:
+285
-61
@@ -197,14 +197,15 @@ func _setup_columns() -> void:
|
||||
_mail_root = mail_tree.create_item()
|
||||
|
||||
# Chat Storage
|
||||
chat_tree.set_column_title(0, "Sender")
|
||||
chat_tree.set_column_title(1, "Content")
|
||||
chat_tree.set_column_title(2, "Date")
|
||||
chat_tree.set_column_title(3, "ID")
|
||||
chat_tree.set_column_custom_minimum_width(0, 100)
|
||||
chat_tree.set_column_expand(1, true)
|
||||
chat_tree.set_column_custom_minimum_width(2, 120)
|
||||
chat_tree.set_column_custom_minimum_width(3, 100)
|
||||
chat_tree.set_column_title(0, "Select")
|
||||
chat_tree.set_column_title(1, "Sender")
|
||||
chat_tree.set_column_title(2, "Content")
|
||||
chat_tree.set_column_title(3, "Date / ID")
|
||||
chat_tree.set_column_custom_minimum_width(0, 70)
|
||||
chat_tree.set_column_expand(0, false)
|
||||
chat_tree.set_column_custom_minimum_width(1, 100)
|
||||
chat_tree.set_column_expand(2, true)
|
||||
chat_tree.set_column_custom_minimum_width(3, 180)
|
||||
_chat_tree_root = chat_tree.create_item()
|
||||
|
||||
func _connect_signals() -> void:
|
||||
@@ -256,8 +257,8 @@ func _connect_signals() -> void:
|
||||
|
||||
# Chat Storage actions
|
||||
load_messages_btn.pressed.connect(_on_load_chat_messages)
|
||||
refresh_chat_btn.pressed.connect(_on_load_chat_messages)
|
||||
delete_selected_btn.pressed.connect(_on_delete_chat_message)
|
||||
refresh_chat_btn.pressed.connect(_on_load_more_chat_messages)
|
||||
delete_selected_btn.pressed.connect(_on_delete_selected_chat_messages)
|
||||
|
||||
# =============================================================================
|
||||
# Core Panel Logic
|
||||
@@ -281,6 +282,8 @@ func _on_tab_changed(tab_index: int) -> void:
|
||||
await _load_leaderboard()
|
||||
elif tab_index == 2:
|
||||
await _load_daily_rewards_config()
|
||||
elif tab_index == 3:
|
||||
_update_announcement_count()
|
||||
elif tab_index == 4:
|
||||
await _load_mail()
|
||||
elif tab_index == 5:
|
||||
@@ -371,12 +374,15 @@ func _on_user_tree_button_clicked(item: TreeItem, _col: int, _id: int, _mouse: i
|
||||
func _show_edit_user_dialog(user: Dictionary) -> void:
|
||||
var uid: String = user.get("user_id", "")
|
||||
var uname: String = user.get("username", "")
|
||||
var display_name: String = user.get("display_name", uname)
|
||||
var role: String = user.get("role", "player")
|
||||
var banned: bool = user.get("banned", false)
|
||||
var detail := await _rpc("admin_get_user_detail", {"user_id": uid})
|
||||
var detail_user: Dictionary = detail.get("user", {}) if not detail.has("error") else {}
|
||||
|
||||
var dialog := AcceptDialog.new()
|
||||
dialog.title = "Edit User: " + uname
|
||||
dialog.min_size = Vector2i(380, 260)
|
||||
dialog.min_size = Vector2i(460, 420)
|
||||
var vbox := VBoxContainer.new()
|
||||
vbox.add_theme_constant_override("separation", 8)
|
||||
|
||||
@@ -385,6 +391,25 @@ func _show_edit_user_dialog(user: Dictionary) -> void:
|
||||
id_lbl.add_theme_color_override("font_color", CLR_DIM)
|
||||
vbox.add_child(id_lbl)
|
||||
|
||||
var email_lbl := Label.new()
|
||||
var email = detail_user.get("email", "")
|
||||
var verified = detail_user.get("email_verified", false)
|
||||
email_lbl.text = "Email: %s (%s)" % [email if not str(email).is_empty() else "none", "verified" if verified else "unverified"]
|
||||
email_lbl.add_theme_color_override("font_color", CLR_DIM)
|
||||
vbox.add_child(email_lbl)
|
||||
|
||||
var name_grid := GridContainer.new()
|
||||
name_grid.columns = 2
|
||||
name_grid.add_theme_constant_override("h_separation", 8)
|
||||
name_grid.add_theme_constant_override("v_separation", 8)
|
||||
var username_lbl := Label.new(); username_lbl.text = "Username:"; name_grid.add_child(username_lbl)
|
||||
var username_input := LineEdit.new(); username_input.text = detail_user.get("username", uname); name_grid.add_child(username_input)
|
||||
var display_lbl := Label.new(); display_lbl.text = "Display Name:"; name_grid.add_child(display_lbl)
|
||||
var display_input := LineEdit.new(); display_input.text = detail_user.get("display_name", display_name); name_grid.add_child(display_input)
|
||||
var password_lbl := Label.new(); password_lbl.text = "New Password:"; name_grid.add_child(password_lbl)
|
||||
var password_input := LineEdit.new(); password_input.placeholder_text = "Leave empty to keep"; password_input.secret = true; name_grid.add_child(password_input)
|
||||
vbox.add_child(name_grid)
|
||||
|
||||
var role_hbox := HBoxContainer.new()
|
||||
var role_lbl := Label.new()
|
||||
role_lbl.text = "Role: "
|
||||
@@ -419,18 +444,31 @@ func _show_edit_user_dialog(user: Dictionary) -> void:
|
||||
|
||||
save_btn.pressed.connect(func():
|
||||
var new_role: String = roles[role_option.selected]
|
||||
await _save_user_edit(uid, uname, new_role, ban_check.button_pressed, reason_input.text)
|
||||
await _save_user_edit(uid, username_input.text.strip_edges(), display_input.text.strip_edges(), password_input.text, new_role, ban_check.button_pressed, reason_input.text)
|
||||
dialog.queue_free()
|
||||
)
|
||||
|
||||
func _save_user_edit(uid: String, uname: String, new_role: String, new_banned: bool, reason: String) -> void:
|
||||
func _save_user_edit(uid: String, uname: String, display_name: String, new_password: String, new_role: String, new_banned: bool, reason: String) -> void:
|
||||
_set_status("Saving...")
|
||||
var identity_res := await _rpc("admin_update_user_identity", {
|
||||
"user_id": uid,
|
||||
"username": uname,
|
||||
"display_name": display_name
|
||||
})
|
||||
if identity_res.has("error"):
|
||||
_set_status("Identity save failed: " + str(identity_res.error), CLR_STATUS_ERR)
|
||||
return
|
||||
if not new_password.strip_edges().is_empty():
|
||||
var password_res := await _rpc("admin_set_user_password", {"user_id": uid, "password": new_password})
|
||||
if password_res.has("error"):
|
||||
_set_status("Password save failed: " + str(password_res.error), CLR_STATUS_ERR)
|
||||
return
|
||||
await _rpc("admin_set_user_role", {"user_id": uid, "role": new_role})
|
||||
if new_banned:
|
||||
await _rpc("admin_ban_player", {"user_id": uid, "reason": reason, "duration_hours": 0})
|
||||
else:
|
||||
await _rpc("admin_unban_player", {"user_id": uid})
|
||||
_set_status("Saved: " + uname, CLR_STATUS_OK)
|
||||
_set_status("Saved: " + display_name, CLR_STATUS_OK)
|
||||
await _load_users()
|
||||
|
||||
# =============================================================================
|
||||
@@ -529,19 +567,47 @@ func _on_history_pressed() -> void:
|
||||
return
|
||||
|
||||
var uid = selected_data[0].get("user_id", "")
|
||||
_set_status("Fetching history for user...", CLR_STATUS_OK)
|
||||
_set_status("Fetching user details...", CLR_STATUS_OK)
|
||||
var detail_res = await _rpc("admin_get_user_detail", {"user_id": uid})
|
||||
var res = await _rpc("admin_get_user_history", {"user_id": uid})
|
||||
|
||||
if res.has("error"):
|
||||
_set_status("Failed to get history: " + str(res.error), CLR_STATUS_ERR)
|
||||
if detail_res.has("error") and res.has("error"):
|
||||
_set_status("Failed to get user details: " + str(detail_res.error), CLR_STATUS_ERR)
|
||||
return
|
||||
|
||||
_set_status("History loaded.", CLR_STATUS_OK)
|
||||
_set_status("User details loaded.", CLR_STATUS_OK)
|
||||
|
||||
var h = res.get("history", {})
|
||||
var text = "[b]=== USER HISTORY ===[/b]\n"
|
||||
var h = res.get("history", {}) if not res.has("error") else {}
|
||||
var details = detail_res if not detail_res.has("error") else {}
|
||||
var detail_user: Dictionary = details.get("user", {})
|
||||
var text = "[b]=== USER DETAIL ===[/b]\n"
|
||||
text += "User ID: " + uid + "\n\n"
|
||||
text += "[b]-- Account --[/b]\n"
|
||||
text += "Username: %s\n" % detail_user.get("username", "")
|
||||
text += "Display Name: %s\n" % detail_user.get("display_name", "")
|
||||
text += "Email: %s (%s)\n" % [detail_user.get("email", "none"), "verified" if detail_user.get("email_verified", false) else "unverified"]
|
||||
text += "Created: %s\n" % str(detail_user.get("create_time", ""))
|
||||
text += "Wallet: %s\n" % str(detail_user.get("wallet", {}))
|
||||
text += "Subscription: %s\n\n" % str(details.get("subscription", {}))
|
||||
|
||||
text += "[b]-- Friends --[/b]\n"
|
||||
var friends = details.get("friends", [])
|
||||
if friends.is_empty():
|
||||
text += "No friends found.\n"
|
||||
else:
|
||||
for f in friends:
|
||||
text += "- %s (%s) state=%s\n" % [f.get("username", ""), f.get("user_id", ""), str(f.get("state", ""))]
|
||||
text += "\n"
|
||||
|
||||
text += "[b]-- Purchase History / Receipts --[/b]\n"
|
||||
var purchases = details.get("purchases", [])
|
||||
if purchases.is_empty():
|
||||
text += "No purchases found.\n"
|
||||
else:
|
||||
for p in purchases:
|
||||
text += "- %s: %s\n" % [p.get("key", ""), str(p.get("value", {}))]
|
||||
text += "\n"
|
||||
|
||||
# Logins
|
||||
text += "[b]-- Recent Logins --[/b]\n"
|
||||
var logins = h.get("logins", [])
|
||||
@@ -575,6 +641,17 @@ func _on_history_pressed() -> void:
|
||||
else:
|
||||
for m in matches:
|
||||
text += "- " + str(m) + "\n"
|
||||
|
||||
text += "\n[b]-- Storage Objects --[/b]\n"
|
||||
var storage = details.get("storage", {})
|
||||
if storage.is_empty():
|
||||
text += "No storage objects found.\n"
|
||||
else:
|
||||
for collection in storage.keys():
|
||||
var objects = storage[collection]
|
||||
text += "\n[b]%s[/b] (%d)\n" % [collection, objects.size()]
|
||||
for obj in objects:
|
||||
text += "- %s: %s\n" % [obj.get("key", ""), str(obj.get("value", {}))]
|
||||
|
||||
history_text.text = text
|
||||
history_dialog.popup_centered()
|
||||
@@ -614,7 +691,15 @@ func _load_leaderboard() -> void:
|
||||
return
|
||||
|
||||
var raw_lb = res.get("leaderboard", [])
|
||||
lb_data = raw_lb if typeof(raw_lb) == TYPE_ARRAY else []
|
||||
if typeof(raw_lb) == TYPE_ARRAY:
|
||||
lb_data = raw_lb
|
||||
elif typeof(raw_lb) == TYPE_DICTIONARY:
|
||||
lb_data = raw_lb.values()
|
||||
else:
|
||||
lb_data = []
|
||||
|
||||
if lb_data.is_empty():
|
||||
lb_data = await _fetch_native_leaderboard_for_admin()
|
||||
count_label.text = "%d records" % lb_data.size()
|
||||
lb_data.sort_custom(func(a, b): return a.get("high_score", 0) > b.get("high_score", 0))
|
||||
|
||||
@@ -631,6 +716,37 @@ func _load_leaderboard() -> void:
|
||||
rank += 1
|
||||
_set_status("")
|
||||
|
||||
func _fetch_native_leaderboard_for_admin() -> Array:
|
||||
var result = await NakamaManager.client.list_leaderboard_records_async(
|
||||
NakamaManager.session,
|
||||
"global_high_score",
|
||||
[],
|
||||
null,
|
||||
100
|
||||
)
|
||||
if result.is_exception():
|
||||
push_warning("[AdminPanel] Native leaderboard load failed: " + result.get_exception().message)
|
||||
return []
|
||||
|
||||
var data: Array = []
|
||||
for record in result.records:
|
||||
var meta: Dictionary = {}
|
||||
if record.metadata and not record.metadata.is_empty():
|
||||
var parsed = JSON.parse_string(record.metadata)
|
||||
if parsed is Dictionary:
|
||||
meta = parsed
|
||||
data.append({
|
||||
"user_id": record.owner_id,
|
||||
"username": record.username,
|
||||
"display_name": record.username if (record.username and not record.username.is_empty()) else "Unknown",
|
||||
"avatar_url": meta.get("avatar_url", ""),
|
||||
"loadout_character": meta.get("loadout_character", "Copper"),
|
||||
"high_score": int(record.score),
|
||||
"games_played": int(meta.get("games_played", 0)),
|
||||
"games_won": int(meta.get("games_won", 0))
|
||||
})
|
||||
return data
|
||||
|
||||
func _on_lb_tree_button_clicked(item: TreeItem, _col: int, _id: int, _mouse: int) -> void:
|
||||
_show_edit_score_dialog(item.get_metadata(0))
|
||||
|
||||
@@ -729,6 +845,7 @@ func _load_daily_rewards_config() -> void:
|
||||
month_option_btn.select(0)
|
||||
_build_dr_grid()
|
||||
|
||||
_update_daily_reward_count()
|
||||
_set_status("Config Loaded", CLR_STATUS_OK)
|
||||
|
||||
func _on_dr_month_selected(index: int) -> void:
|
||||
@@ -737,6 +854,7 @@ func _on_dr_month_selected(index: int) -> void:
|
||||
|
||||
_current_dr_month = month_option_btn.get_item_metadata(index)
|
||||
_build_dr_grid()
|
||||
_update_daily_reward_count()
|
||||
|
||||
func _save_current_grid_to_dict() -> void:
|
||||
if _current_dr_month.is_empty(): return
|
||||
@@ -784,6 +902,11 @@ func _build_dr_grid() -> void:
|
||||
spin.value = int(rdata)
|
||||
opt.select(0)
|
||||
|
||||
func _update_daily_reward_count() -> void:
|
||||
var rewards = _daily_reward_config_data.get(_current_dr_month, [])
|
||||
var count: int = rewards.size() if typeof(rewards) == TYPE_ARRAY else 0
|
||||
count_label.text = "%d reward days" % count
|
||||
|
||||
func _save_daily_rewards_config() -> void:
|
||||
_save_current_grid_to_dict()
|
||||
_set_status("Saving config...")
|
||||
@@ -802,7 +925,15 @@ func _on_add_reward_pressed() -> void:
|
||||
row.visible = true
|
||||
rewards_list.add_child(row)
|
||||
var remove_btn = row.get_node("RemoveBtn") as Button
|
||||
remove_btn.pressed.connect(func(): row.queue_free())
|
||||
remove_btn.pressed.connect(func(): row.queue_free(); _update_announcement_count())
|
||||
_update_announcement_count()
|
||||
|
||||
func _update_announcement_count() -> void:
|
||||
var count := 0
|
||||
for child in rewards_list.get_children():
|
||||
if child.visible:
|
||||
count += 1
|
||||
count_label.text = "%d rewards attached" % count
|
||||
|
||||
func _on_find_user() -> void:
|
||||
var input = target_user_edit.text.strip_edges()
|
||||
@@ -927,6 +1058,7 @@ func _on_send_mail() -> void:
|
||||
end_date_edit.clear_date()
|
||||
for child in rewards_list.get_children():
|
||||
if child.visible: child.queue_free()
|
||||
_update_announcement_count()
|
||||
|
||||
# =============================================================================
|
||||
# TAB 5: MAIL MANAGER
|
||||
@@ -1245,6 +1377,7 @@ func _save_featured_banners() -> void:
|
||||
# =============================================================================
|
||||
func _load_chat_config() -> void:
|
||||
chat_status_label.text = "Loading config..."
|
||||
count_label.text = "chat config"
|
||||
var res := await _rpc("admin_get_chat_config", {})
|
||||
if res.has("error"):
|
||||
chat_status_label.text = "Failed: " + str(res.error)
|
||||
@@ -1344,18 +1477,19 @@ func _on_save_chat_config() -> void:
|
||||
func _on_load_chat_messages() -> void:
|
||||
var channel_id := chat_channel_id_edit.text.strip_edges()
|
||||
if channel_id.is_empty():
|
||||
_set_status("Enter a Channel ID first.", CLR_STATUS_ERR)
|
||||
return
|
||||
# Default to the global lobby room rather than erroring out.
|
||||
channel_id = "social_global"
|
||||
chat_channel_id_edit.text = channel_id
|
||||
|
||||
# Auto-resolve "social_global" to the actual Nakama Channel ID if the admin is in the lobby
|
||||
# Best-effort: resolve "social_global" to the real hashed Nakama Channel ID so
|
||||
# the admin sees it in the UI. If resolution fails (not in lobby / socket
|
||||
# down), fall through with the room name — the server resolves it
|
||||
# authoritatively via nk.channel_id_build.
|
||||
if channel_id == "social_global":
|
||||
var lobby = get_tree().get_first_node_in_group("Lobby")
|
||||
if lobby and lobby.get("chat") and lobby.chat.get("_chat_channel"):
|
||||
channel_id = lobby.chat._chat_channel.id
|
||||
chat_channel_id_edit.text = channel_id # Update UI so admin sees the real ID
|
||||
else:
|
||||
_set_status("Cannot resolve social_global. Join chat first.", CLR_STATUS_ERR)
|
||||
return
|
||||
var resolved := await _resolve_global_chat_channel_id()
|
||||
if not resolved.is_empty():
|
||||
channel_id = resolved
|
||||
chat_channel_id_edit.text = channel_id # show the admin the real ID
|
||||
|
||||
_chat_channel_id = channel_id
|
||||
_chat_cursor = ""
|
||||
@@ -1364,6 +1498,35 @@ func _on_load_chat_messages() -> void:
|
||||
|
||||
await _fetch_chat_messages_batch()
|
||||
|
||||
func _resolve_global_chat_channel_id() -> String:
|
||||
# Nakama Room channel IDs are deterministically hashed from the type and room name.
|
||||
# For type=2 (Room) and name="social_global", the ID format is always:
|
||||
# "2." + uri_encoded_room_name + "." # no domain needed for rooms.
|
||||
# But Nakama's format often just uses "2.RoomName." - let's ensure we try the exact determinism if socket fails.
|
||||
|
||||
var lobby = get_tree().get_first_node_in_group("Lobby")
|
||||
if lobby and lobby.get("chat") and lobby.chat.get("_chat_channel"):
|
||||
return lobby.chat._chat_channel.id
|
||||
|
||||
var socket = NakamaManager.socket
|
||||
if socket and socket.is_connected_to_host():
|
||||
var result = await socket.join_chat_async("social_global", NakamaSocket.ChannelType.Room, true, false)
|
||||
if not result.is_exception():
|
||||
return result.id
|
||||
|
||||
# Fallback if no socket or join failed: construct the exact ID the Web UI expects.
|
||||
# Type 2 (Room), Name "social_global"
|
||||
return "2." + "social_global".uri_encode() + "."
|
||||
|
||||
func _on_load_more_chat_messages() -> void:
|
||||
if _chat_channel_id.is_empty():
|
||||
await _on_load_chat_messages()
|
||||
return
|
||||
if _chat_cursor.is_empty():
|
||||
_set_status("No more messages to load.", CLR_STATUS_OK)
|
||||
return
|
||||
await _fetch_chat_messages_batch()
|
||||
|
||||
func _fetch_chat_messages_batch() -> void:
|
||||
_set_status("Loading messages...")
|
||||
var payload := {
|
||||
@@ -1380,19 +1543,42 @@ func _fetch_chat_messages_batch() -> void:
|
||||
|
||||
var msgs = res.get("messages", [])
|
||||
var next_cursor = res.get("next_cursor", "")
|
||||
if (typeof(msgs) == TYPE_DICTIONARY and msgs.is_empty()) or (typeof(msgs) == TYPE_ARRAY and msgs.is_empty()):
|
||||
var fallback = await NakamaManager.client.list_channel_messages_async(NakamaManager.session, _chat_channel_id, 50, false, _chat_cursor)
|
||||
if not fallback.is_exception():
|
||||
msgs = fallback.messages if fallback.messages else []
|
||||
next_cursor = fallback.next_cursor
|
||||
else:
|
||||
_set_status("Failed: " + fallback.get_exception().message, CLR_STATUS_ERR)
|
||||
return
|
||||
if typeof(msgs) == TYPE_DICTIONARY:
|
||||
msgs = msgs.values()
|
||||
elif typeof(msgs) != TYPE_ARRAY:
|
||||
msgs = []
|
||||
var added_count := 0
|
||||
|
||||
for msg in msgs:
|
||||
for raw_msg in msgs:
|
||||
var msg := _normalize_chat_storage_message(raw_msg)
|
||||
if msg.is_empty():
|
||||
continue
|
||||
_chat_messages_data.append(msg)
|
||||
var item := _chat_tree_root.create_child()
|
||||
item.set_text(0, msg.get("username", msg.get("sender_id", "?").substr(0, 8)))
|
||||
item.set_text(1, msg.get("content", ""))
|
||||
item.set_text(2, msg.get("create_time", "").substr(0, 19).replace("T", " "))
|
||||
item.set_cell_mode(0, TreeItem.CELL_MODE_CHECK)
|
||||
item.set_editable(0, true)
|
||||
item.set_text(1, msg.get("username", msg.get("sender_id", "?").substr(0, 8)))
|
||||
item.set_text(2, _format_chat_storage_content(msg.get("content", "")))
|
||||
item.set_text(3, msg.get("create_time", "").substr(0, 19).replace("T", " "))
|
||||
var mid = msg.get("message_id", "")
|
||||
item.set_text(3, mid)
|
||||
item.set_tooltip_text(3, mid)
|
||||
item.set_metadata(0, msg)
|
||||
added_count += 1
|
||||
|
||||
count_label.text = "%d messages loaded" % _chat_messages_data.size()
|
||||
chat_tree.queue_redraw()
|
||||
|
||||
if added_count == 0:
|
||||
_set_status("No stored messages returned for channel: " + _chat_channel_id, CLR_STATUS_ERR)
|
||||
return
|
||||
|
||||
if not next_cursor.is_empty():
|
||||
_chat_cursor = next_cursor
|
||||
@@ -1401,37 +1587,75 @@ func _fetch_chat_messages_batch() -> void:
|
||||
_chat_cursor = ""
|
||||
_set_status("All messages loaded.", CLR_STATUS_OK)
|
||||
|
||||
func _on_delete_chat_message() -> void:
|
||||
var item = chat_tree.get_selected()
|
||||
if not item:
|
||||
_set_status("Select a message to delete.", CLR_STATUS_ERR)
|
||||
return
|
||||
func _format_chat_storage_content(content) -> String:
|
||||
if typeof(content) == TYPE_DICTIONARY:
|
||||
return str(content.get("msg", content))
|
||||
|
||||
var msg = item.get_metadata(0)
|
||||
if not msg:
|
||||
return
|
||||
var text := str(content)
|
||||
var parsed = JSON.parse_string(text)
|
||||
if typeof(parsed) == TYPE_DICTIONARY:
|
||||
return str(parsed.get("msg", text))
|
||||
return text
|
||||
|
||||
var msg_id = msg.get("message_id", "")
|
||||
if msg_id.is_empty():
|
||||
func _normalize_chat_storage_message(raw_msg) -> Dictionary:
|
||||
if typeof(raw_msg) == TYPE_DICTIONARY:
|
||||
return raw_msg
|
||||
if typeof(raw_msg) != TYPE_OBJECT:
|
||||
return {}
|
||||
return {
|
||||
"message_id": raw_msg.message_id,
|
||||
"sender_id": raw_msg.sender_id,
|
||||
"username": raw_msg.username,
|
||||
"content": raw_msg.content,
|
||||
"create_time": raw_msg.create_time,
|
||||
"update_time": raw_msg.update_time,
|
||||
"channel_id": raw_msg.channel_id
|
||||
}
|
||||
|
||||
func _on_delete_selected_chat_messages() -> void:
|
||||
var items := _get_checked_chat_items()
|
||||
if items.is_empty():
|
||||
var selected = chat_tree.get_selected()
|
||||
if selected:
|
||||
items.append(selected)
|
||||
if items.is_empty():
|
||||
_set_status("Select one or more messages to delete.", CLR_STATUS_ERR)
|
||||
return
|
||||
|
||||
var confirm := ConfirmationDialog.new()
|
||||
confirm.title = "Delete Message?"
|
||||
confirm.dialog_text = "Permanently delete message from " + msg.get("username", "?") + "?"
|
||||
confirm.title = "Delete %d Message(s)?" % items.size()
|
||||
confirm.dialog_text = "Permanently delete selected chat messages?"
|
||||
add_child(confirm)
|
||||
confirm.popup_centered()
|
||||
confirm.confirmed.connect(func():
|
||||
_set_status("Deleting message...")
|
||||
var res = await _rpc("admin_delete_channel_message", {
|
||||
"channel_id": _chat_channel_id,
|
||||
"message_id": msg_id
|
||||
})
|
||||
if res.get("success", false):
|
||||
_set_status("Message deleted!", CLR_STATUS_OK)
|
||||
chat_tree.get_root().remove_child(item)
|
||||
item.free()
|
||||
count_label.text = "%d messages loaded" % _chat_messages_data.size()
|
||||
else:
|
||||
_set_status("Failed: " + str(res.get("error", "")), CLR_STATUS_ERR)
|
||||
_set_status("Deleting %d message(s)..." % items.size())
|
||||
var deleted := 0
|
||||
for item in items:
|
||||
var msg = item.get_metadata(0)
|
||||
if typeof(msg) != TYPE_DICTIONARY:
|
||||
continue
|
||||
var msg_id = msg.get("message_id", "")
|
||||
if msg_id.is_empty():
|
||||
continue
|
||||
var res = await _rpc("admin_delete_channel_message", {
|
||||
"channel_id": _chat_channel_id,
|
||||
"message_id": msg_id
|
||||
})
|
||||
if res.get("success", false):
|
||||
deleted += 1
|
||||
_chat_messages_data.erase(msg)
|
||||
_chat_tree_root.remove_child(item)
|
||||
item.free()
|
||||
count_label.text = "%d messages loaded" % _chat_messages_data.size()
|
||||
_set_status("Deleted %d message(s)" % deleted, CLR_STATUS_OK if deleted > 0 else CLR_STATUS_ERR)
|
||||
confirm.queue_free()
|
||||
)
|
||||
|
||||
func _get_checked_chat_items() -> Array:
|
||||
var items: Array = []
|
||||
var child = _chat_tree_root.get_first_child() if _chat_tree_root else null
|
||||
while child:
|
||||
if child.is_checked(0):
|
||||
items.append(child)
|
||||
child = child.get_next()
|
||||
return items
|
||||
|
||||
Reference in New Issue
Block a user