diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index c7f7132028f0..5bfc8b2c0e51 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -201,235 +201,11 @@ var/global/BSACooldown = 0 feedback_add_details("admin_verb","SPP") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - -#define PLAYER_INFO_MISSING_CONTENT_TEXT "Missing Data" -#define PLAYER_INFO_MISSING_AUTHOR_TEXT "N/A" -#define PLAYER_INFO_MISSING_RANK_TEXT "N/A" -#define PLAYER_INFO_MISSING_TIMESTAMP_TEXT "N/A" -#define PLAYER_INFO_MISSING_JOB_TEXT "N/A" -#define PLAYER_INFO_MISSING_ROUND_ID_TEXT "N/A" - -/datum/player_info - var/author = PLAYER_INFO_MISSING_AUTHOR_TEXT // admin who authored the information - var/content = PLAYER_INFO_MISSING_CONTENT_TEXT // text content of the information - var/timestamp = PLAYER_INFO_MISSING_TIMESTAMP_TEXT // Because this is bloody annoying - var/days_timestamp = 0 // number of day after 1 Jan 2000 - var/round_id = PLAYER_INFO_MISSING_ROUND_ID_TEXT - var/ingameage = 0 - -/datum/player_info/proc/get_days_timestamp() - return isnum(days_timestamp) ? days_timestamp : 0 - /datum/admins/proc/show_player_notes(key) if(!(check_rights(R_LOG) && check_rights(R_BAN))) return - key = ckey(key) - - if(!key || !config.sql_enabled) - return - - if(!establish_db_connection("erro_messages", "erro_ban")) - to_chat(usr, "Notes [key] from DB don't available.") - return - - //Display player age and player warn bans - var/p_age - var/p_ingame_age - for(var/client/C in clients) - if(C.ckey == key) - p_age = C.player_age - p_ingame_age = C.player_ingame_age - - // Gather data - var/list/db_messages = load_info_player_db_messages(key) - var/list/db_bans = load_info_player_db_bans(key) - // Start render info page - var/dat = "" - dat +="Player age: [p_age] / In-game age: [p_ingame_age]
" - - if(!length(db_messages) && !length(db_bans)) - dat += "No information found on the given key.
" - else - var/list/infos = generalized_players_info(db_messages, db_bans) - for(var/datum/player_info/I in infos) - dat += "[I.content] by [I.author] on #[I.round_id], [I.timestamp] ([I.ingameage] player minutes) " - dat += "

" - dat += "
" - dat += "Add Comment
" - - var/datum/browser/popup = new(usr, "window=adminplayerinfo", "Info on [key]", 480, 480, ntheme = CSS_THEME_LIGHT) - popup.set_content(dat) - popup.open() - -/datum/admins/proc/generalized_players_info(list/file_notes, list/db_notes) - var/list/datum/player_info/merged = list() - if(length(file_notes)) - merged += file_notes - if(length(db_notes)) - merged += db_notes - merged = sortMerge(merged, GLOBAL_PROC_REF(cmp_days_timestamp), FALSE) - return merged - -/proc/cmp_days_timestamp(datum/player_info/a, datum/player_info/b) - return a.get_days_timestamp() - b.get_days_timestamp() - -/datum/admins/proc/load_info_player_db_messages(player_ckey) - // Get player ckey and generate list of players_notes - // Return null if errors - var/list/db_player_notes = list() - var/timestamp_format = "%a, %M %D of %Y" // we don't really need it now because both bans and notes use normal timestamp, but i'm little tired - var/days_ago_start_date = "1999-12-31" // to make changes here ang test, and anyway we will rewrite it completely - var/list/sql_fields = list( - "adminckey", - "text", - "DATE_FORMAT(timestamp, '[timestamp_format]')", - "DATEDIFF(timestamp, '[days_ago_start_date]')", - "round_id", - "ingameage" - ) - var/DBQuery/query = dbcon.NewQuery("SELECT " + sql_fields.Join(", ") + " FROM erro_messages WHERE (targetckey = '[ckey(player_ckey)]') AND (deleted = 0) ORDER BY id LIMIT 100") - if(!query.Execute()) - return - while(query.NextRow()) - var/datum/player_info/notes_record = new() - - var/a_ckey = query.item[1] - var/text = query.item[2] - var/timestamp = query.item[3] - var/days_ago = text2num(query.item[4]) - var/rid = text2num(query.item[5]) - var/ingameage = text2num(query.item[6]) - - if(length(a_ckey)) - notes_record.author = a_ckey - if(length(text)) - notes_record.content = text - if(length(timestamp)) - notes_record.timestamp = timestamp - if(days_ago) - notes_record.days_timestamp = days_ago - if(rid) - notes_record.round_id = rid - if(ingameage) - notes_record.ingameage = ingameage - - db_player_notes += notes_record - - return db_player_notes - -/datum/admins/proc/load_info_player_db_bans(player_ckey) - // Get player ckey and generate list of players_notes - // Return null if errors - var/list/db_player_notes = list() - var/timestamp_format = "%a, %M %D of %Y" - var/days_ago_start_date = "1999-12-31" - var/list/sql_fields = list( - "a_ckey", - "bantype", - "reason", - "DATE_FORMAT(bantime, '[timestamp_format]')", - "ip", - "computerid", - "duration", - "job", - "DATEDIFF(bantime, '[days_ago_start_date]')", - "unbanned", - "DATE_FORMAT(unbanned_datetime, '[timestamp_format]')", - "DATEDIFF(unbanned_datetime, '[days_ago_start_date]')", - "unbanned_ckey", - "round_id", - "ingameage" - ) - var/DBQuery/query = dbcon.NewQuery("SELECT " + sql_fields.Join(", ") + " FROM erro_ban WHERE (ckey = '[ckey(player_ckey)]') ORDER BY id LIMIT 100") - if(!query.Execute()) - return - while(query.NextRow()) - var/datum/player_info/notes_record = new() - var/datum/player_info/unban_notes_record - var/list/ip_cid = list() - var/a_ckey = query.item[1] - var/bantype = query.item[2] - var/reason = query.item[3] - var/timestamp = query.item[4] - if(query.item[5]) - ip_cid += query.item[5] - if(query.item[6]) - ip_cid += query.item[6] - var/duration = text2num(query.item[7]) - var/job = query.item[8] ? query.item[8] : PLAYER_INFO_MISSING_JOB_TEXT - var/days_ago = text2num(query.item[9]) - var/is_unbanned = query.item[10] ? TRUE : FALSE - var/unbanned_timestamp = query.item[11] - var/unbanned_days_ago = text2num(query.item[12]) - var/unbanned_a_ckey = query.item[13] - var/rid = text2num(query.item[14]) - var/ingameage = text2num(query.item[15]) - - if(rid) - notes_record.round_id = rid - - if(ingameage) - notes_record.round_id = ingameage - - // -1 = perma, duration in minutes come - if(!duration) - duration = "N/A" - else if(duration < 0) - duration = "infinity" - else - duration = DisplayTimeText((duration MINUTE), 1) - - // Ban Record creating - if(length(a_ckey)) - notes_record.author = a_ckey - var/description = "([ip_cid.Join(", ")]): [reason]" - switch(bantype) - if (BANTYPE_JOB_PERMA) - // notes_record.content = "Permanent JOB BAN [job] [description]" - // already in notes by Adminbot - continue - if (BANTYPE_JOB_TEMP) - // notes_record.content = "Temporal JOB BAN [job] for [duration] [description]" - continue - if (BANTYPE_PERMA) - notes_record.content = "Permanent BAN [description]" - if (BANTYPE_TEMP) - notes_record.content = "Temporal BAN for [duration] [description]" - if(length(timestamp)) - notes_record.timestamp = timestamp - if(days_ago) - notes_record.days_timestamp = days_ago - db_player_notes += notes_record - - // Unban record creating - if(is_unbanned) - unban_notes_record = new() - if(length(unbanned_a_ckey)) - unban_notes_record.author = unbanned_a_ckey - switch(bantype) - if(BANTYPE_JOB_PERMA) - unban_notes_record.content = "Unban. Permanent JOB BAN [job] was [timestamp]" - if(BANTYPE_JOB_TEMP) - unban_notes_record.content = "Unban. Temporal JOB BAN [job] was [timestamp]" - if(BANTYPE_PERMA) - unban_notes_record.content = "Unban. Permanent BAN was [timestamp]" - if(BANTYPE_TEMP) - unban_notes_record.content = "Unban. Temporal BAN was [timestamp]" - if(length(unbanned_timestamp)) - unban_notes_record.timestamp = unbanned_timestamp - if(unbanned_days_ago) - unban_notes_record.days_timestamp = unbanned_days_ago - db_player_notes += unban_notes_record - return db_player_notes - -#undef PLAYER_INFO_MISSING_ROUND_ID_TEXT -#undef PLAYER_INFO_MISSING_CONTENT_TEXT -#undef PLAYER_INFO_MISSING_AUTHOR_TEXT -#undef PLAYER_INFO_MISSING_RANK_TEXT -#undef PLAYER_INFO_MISSING_TIMESTAMP_TEXT -#undef PLAYER_INFO_MISSING_JOB_TEXT - + notes_panel(key) /datum/admins/proc/access_news_network() //MARKER set category = "Fun" diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 77ab78cc6dfa..c88211e92e56 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -551,7 +551,7 @@ var/global/list/admin_verbs_hideable = list( if(!warned_ckey || !reason) return - notes_add(warned_ckey, "ADMINWARN: " + reason, src, secret = 0) + notes_add(warned_ckey, "ADMINWARN: " + reason, admin_key = src.ckey, secret = 0) var/client/C = directory[warned_ckey] reason = sanitize(reason) diff --git a/code/modules/admin/notes_panel.dm b/code/modules/admin/notes_panel.dm new file mode 100644 index 000000000000..d166dc1f138d --- /dev/null +++ b/code/modules/admin/notes_panel.dm @@ -0,0 +1,121 @@ +/proc/notes_panel(ckey) // change to proc player_access = FALSE + if(!(check_rights(R_LOG) && check_rights(R_BAN))) + return + + if(!establish_db_connection("erro_messages", "erro_player")) + return + + var/sql_ckey = ckey(ckey) + + if(!sql_ckey) + return + + var/html = "" + + var/player_ingame_age + var/player_age + var/offline = TRUE + + if(global.directory[ckey]) + var/client/C = global.directory[ckey] + player_ingame_age = C.player_ingame_age + player_age = C.player_age + offline = FALSE + else + var/DBQuery/player_query = dbcon.NewQuery("SELECT datediff(Now(), firstseen) as age, ingameage FROM erro_player WHERE ckey = '[sql_ckey]'") + player_query.Execute() + + while(player_query.NextRow()) + player_age = text2num(player_query.item[1]) + player_ingame_age = text2num(player_query.item[2]) + break + + html += {" +
+ Add new message + [offline ? "Offline" : "Online"] / + Player age: [player_age] / In-game age: [player_ingame_age] +

+
+ "} + + // todo: use mysql DATE_FORMAT(timestamp, '%d.%m.%Y %H:%i:%s') after bans table rework (consistent column names, also need to allow job as null) + var/DBQuery/query = dbcon.NewQuery({" + SELECT id as message_id, type AS message_type, text AS message, timestamp, ingameage, adminckey AS author, round_id FROM erro_messages WHERE targetckey = '[sql_ckey]' AND deleted != 1 + UNION ALL + SELECT NULL as message_id, bantype AS message_type, CONCAT_WS(' | Job: ', reason, NULLIF(job,'')) AS message, bantime AS timestamp, ingameage, a_ckey AS author, round_id FROM erro_ban WHERE ckey = '[sql_ckey]' + ORDER by timestamp DESC + LIMIT 50; + "}) // todo: pager + + if(!query.Execute()) + return + + var/message_id + var/message_type + var/message + var/timestamp + var/ingameage + var/author + var/round_id + + var/age_temperature + var/border_color + var/static/list/type_hex_colors = list( + "note" = "#00ffff", + lowertext(BANTYPE_PERMA) = "#b00000", + lowertext(BANTYPE_TEMP) = "#ff0000", + lowertext(BANTYPE_JOB_PERMA) = "#ff8c00", + lowertext(BANTYPE_JOB_TEMP) = "#ffa500", + ) + + var/buttons + + while(query.NextRow()) + message_id = query.item[1] + message_type = lowertext(query.item[2]) + message = query.item[3] + timestamp = query.item[4] + ingameage = text2num(query.item[5]) + author = query.item[6] + round_id = query.item[7] ? "#"+query.item[7] : "" + + // heat color for recent messages + if(player_ingame_age && ingameage) + // if diff 5000 minutes or more - green + // if diff close to 0 - red + age_temperature = clamp(floor(((player_ingame_age - ingameage) * 100) / 5000), 0, 100) + else + age_temperature = 100 + + if(type_hex_colors[message_type]) + border_color = type_hex_colors[message_type] + else + border_color = null + + if(message_type == "note" && message_id) + buttons = {" +
+ E R +
+ "} + else // bans + buttons = {" +
+ V +
+ "} + + // todo: move styles to own css + html += {" +
+ [buttons] + [message]
+
+ Type: [message_type]; Date: [timestamp] [round_id];
Minutes: [ingameage]; By: [author] +
+ "} + + var/datum/browser/popup = new(usr, "[sql_ckey]_notes_history", "[ckey] notes history", 700, 700, ntheme = CSS_THEME_LIGHT) + popup.set_content(html) + popup.open() diff --git a/code/modules/admin/player_notes.dm b/code/modules/admin/player_notes.dm index a9c2a5c77955..96a28ed9fa29 100644 --- a/code/modules/admin/player_notes.dm +++ b/code/modules/admin/player_notes.dm @@ -1,18 +1,21 @@ -/proc/notes_add(key, note, client/admin, secret = 1) +// just common methods to work with messages +// can be called from bots so does not check permissions/etc. +// you should do it yourself + +/proc/notes_add(key, note, admin_key, secret = 1) + if(!establish_db_connection("erro_messages")) + return + key = ckey(key) note = sanitize(note) + admin_key = ckey(admin_key) if (!key || !note) return - if(!(check_rights(R_LOG) && check_rights(R_BAN))) - return + if(!admin_key) + admin_key = "Adminbot" - - if(!establish_db_connection("erro_messages")) - return - - var/admin_key = admin ? ckey(admin.ckey) : "Adminbot" secret = !!secret var/ingameage = 0 @@ -28,6 +31,34 @@ var/DBQuery/new_notes = dbcon.NewQuery(sql) new_notes.Execute() - message_admins("[admin ? key_name_admin(admin) : "Adminbot"] has edited [key]'s notes.") - log_admin("[admin ? key_name(admin) : "Adminbot"] has edited [key]'s notes.") - admin_ticket_log(key, "[admin ? key_name(admin) : "Adminbot"] has edited [key]'s notes: [note]") + admin_ticket_log(key, "[admin_key] has edited [key]'s notes: [note]") + +/proc/notes_delete(id, admin_key) + if(!establish_db_connection("erro_messages")) + return + + id = text2num(id) + admin_key = ckey(admin_key) + + if(!id || !admin_key) + return + + var/DBQuery/query = dbcon.NewQuery({"UPDATE erro_messages + SET deleted = 1, deleted_ckey = '[admin_key]' + WHERE id = [id]"}) + query.Execute() + +/proc/notes_edit(id, new_note) + if(!establish_db_connection("erro_messages")) + return + + id = text2num(id) + new_note = sanitize(new_note) + + if(!id || !new_note) + return + + var/DBQuery/query = dbcon.NewQuery({"UPDATE erro_messages + SET text = '[sanitize_sql(new_note)]' + WHERE id = [id]"}) + query.Execute() diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index d5b99dff8fe3..c40cdc80a67c 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -2273,21 +2273,87 @@ // player info stuff - else if(href_list["add_player_info"]) - var/key = ckey(href_list["add_player_info"]) - var/add = input("Add Player Info") as null|text//sanitise below in notes_add - if(!add) return + else if(href_list["notes_add"]) + if(!(check_rights(R_LOG) && check_rights(R_BAN))) + return - notes_add(key, add, usr.client) + var/key = ckey(href_list["notes_add"]) + var/message = input("Add new notice message to [key]") as null|text//sanitise below in notes_add + if(!message) + return + + notes_add(key, message, usr.client.ckey) show_player_notes(key) - /* unimplemented - if(href_list["remove_player_info"]) - var/key = ckey(href_list["remove_player_info"]) - var/index = text2num(href_list["remove_index"]) + message_admins("[key_name_admin(usr)] has edited [key]'s notes.") + log_admin("[key_name(usr)] has edited [key]'s notes.") + + if(href_list["notes_delete"]) + if(!(check_rights(R_LOG) && check_rights(R_BAN))) + return + + var/key = ckey(href_list["notes_delete"]) + var/id = text2num(href_list["index"]) + + var/DBQuery/query = dbcon.NewQuery({"SELECT type, adminckey, text + FROM erro_messages + WHERE id='[id]' AND deleted=0"}) + query.Execute() + + if(!query.NextRow()) + to_chat(usr, "Message does not exist or already deleted.") + return + + var/notetype = query.item[1] + var/admin = query.item[2] + var/text = query.item[3] + + if(!(admin == usr.client.ckey || check_rights(R_PERMISSIONS))) + tgui_alert(usr, "You don't have permissions to delete other people messages!", "No permissions") + return + + if(tgui_alert(usr, "Are you really want to delete next message: [text]; by [admin]?", "Confirm", list("Yes", "No")) != "Yes") + return + + message_admins("[key_name_admin(usr)] has deleted [key] note [notetype] by [admin] with text: [text].") + log_admin("[key_name(usr)] has deleted [key] note [notetype] by [admin] with text: [text].") - notes_del(key, index, usr.client) - show_player_notes(key)*/ + notes_delete(id, usr.client.ckey) + show_player_notes(key) + + if(href_list["notes_edit"]) + if(!(check_rights(R_LOG) && check_rights(R_BAN))) + return + + var/key = ckey(href_list["notes_edit"]) + var/id = text2num(href_list["index"]) + + var/DBQuery/query = dbcon.NewQuery({"SELECT type, adminckey, text + FROM erro_messages + WHERE id='[id]' AND deleted=0"}) + query.Execute() + + if(!query.NextRow()) + to_chat(usr, "Message does not exist or already deleted.") + return + + var/notetype = query.item[1] + var/admin = query.item[2] + var/text = query.item[3] + + if(!(admin == usr.client.ckey || check_rights(R_PERMISSIONS))) + tgui_alert(usr, "You don't have permissions to edit other people messages!", "No permissions") + return + + var/new_message = input("Edit message", html_decode(text)) as null|text//sanitise below in notes_add + if(!new_message) + return + + message_admins("[key_name_admin(usr)] has edited [key] note [notetype] by [admin].") + log_admin("[key_name(usr)] has edited [key] note [notetype] by [admin].") + + notes_edit(id, new_message) + show_player_notes(key) else if(href_list["notes"]) var/ckey = ckey(href_list["ckey"]) diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 09f6406f74d1..2ebfbc878cb9 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -199,7 +199,7 @@ M.client.prefs.permamuted |= mute_type M.client.prefs.save_preferences() M.client.prefs.muted |= mute_type - notes_add(M.ckey, "Permamute from [mute_string]: [permmutreason]", usr.client) + notes_add(M.ckey, "Permamute from [mute_string]: [permmutreason]", admin_key = usr.client.ckey) permmutreason = sanitize(permmutreason) to_chat(M, "You have been permamuted from [mute_string] by [usr.key].
Reason: [permmutreason]
") else @@ -209,7 +209,7 @@ else if (tgui_alert(usr, "Add a notice for round mute?", "Mute Notice?", list("Yes","No")) == "Yes") var/mutereason = input("Mute Reason") as text|null if(mutereason) - notes_add(M.ckey, "Muted from [mute_string]: [mutereason]", usr.client) + notes_add(M.ckey, "Muted from [mute_string]: [mutereason]", admin_key = usr.client.ckey) mutereason = sanitize(mutereason) to_chat(M, "You have been muted from [mute_string] by [usr.key].
Reason: [mutereason]
") else @@ -1076,7 +1076,7 @@ Traitors and the like can also be revived with the previous role mostly intact. return if(target.player_ingame_age < value) - notes_add(target.ckey, "PLAYERAGE: increased in-game age from [target.player_ingame_age] to [value]", src, secret = 0) + notes_add(target.ckey, "PLAYERAGE: increased in-game age from [target.player_ingame_age] to [value]", admin_key = ckey, secret = 0) log_admin("[key_name(usr)] increased [key_name(target)] in-game age from [target.player_ingame_age] to [value]") message_admins("[key_name_admin(usr)] increased [key_name_admin(target)] in-game age from [target.player_ingame_age] to [value]") diff --git a/code/modules/bridge/commands/notes.dm b/code/modules/bridge/commands/notes.dm index 263a5ab6484f..f580adf1728e 100644 --- a/code/modules/bridge/commands/notes.dm +++ b/code/modules/bridge/commands/notes.dm @@ -55,50 +55,3 @@ attachment_msg = "Notes of **[ckey]**, offset **[offset]**, requested by <@![params["bridge_from_uid"]]>\n-----\n[message]", attachment_color = BRIDGE_COLOR_BRIDGE, ) - -/datum/bridge_command/notedel - name = "notedel" - desc = "Delete player note by ID. You can find ID with ``noteslist``" - format = "@Bot notedel %ckey% %noteid%" - example = "@Bot notedel taukitty 123" - position = 71 - -/datum/bridge_command/notedel/execute(list/params) - var/ckey = ckey(params["bridge_arg_1"]) - var/id = text2num(params["bridge_arg_2"]) - - if(!ckey || !id || !establish_db_connection("erro_messages")) - return - - var/DBQuery/select_query = dbcon.NewQuery({"SELECT type, adminckey, text - FROM erro_messages - WHERE id='[id]' AND deleted=0"}) - select_query.Execute() - - if(!select_query.NextRow()) - world.send2bridge( - type = list(BRIDGE_ADMINCOM), - attachment_title = "Bridge: Notedel", - attachment_msg = "<@![params["bridge_from_uid"]]> wrong note ID or note already deleted", - attachment_color = BRIDGE_COLOR_BRIDGE, - ) - return - - var/notetype = select_query.item[1] - var/admin = select_query.item[2] - var/text = select_query.item[3] - - var/DBQuery/update_query = dbcon.NewQuery({"UPDATE erro_messages - SET deleted = 1, deleted_ckey = '[BRIDGE_FROM_SNIPPET_DB]' - WHERE id = [id]"}) - update_query.Execute() - - world.send2bridge( - type = list(BRIDGE_ADMINCOM), - attachment_title = "Bridge: Notedel", - attachment_msg = "<@![params["bridge_from_uid"]]> has deleted **[ckey]**'s note:\n[notetype] by [admin] with text:\n*[text]*", - attachment_color = BRIDGE_COLOR_ADMINBAN, - ) - - log_admin("[BRIDGE_FROM_SNIPPET_TEXT] has deleted [ckey] note [notetype] by [admin] with text: [text].") - message_admins("[BRIDGE_FROM_SNIPPET_HTML] has deleted [ckey] note [notetype] by [admin] with text: [text].") diff --git a/taucetistation.dme b/taucetistation.dme index e6b59a2c0549..a27a681b1363 100644 --- a/taucetistation.dme +++ b/taucetistation.dme @@ -1334,6 +1334,7 @@ #include "code\modules\admin\holder2.dm" #include "code\modules\admin\host_announcements.dm" #include "code\modules\admin\IsBanned.dm" +#include "code\modules\admin\notes_panel.dm" #include "code\modules\admin\player_notes.dm" #include "code\modules\admin\player_panel.dm" #include "code\modules\admin\secrets.dm"