diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 7c2f550dc409..f7734502d652 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -441,3 +441,8 @@ GLOBAL_LIST_INIT(ghost_others_options, list(GHOST_OTHERS_SIMPLE, GHOST_OTHERS_DE #define MOUSE_OPACITY_TRANSPARENT 0 #define MOUSE_OPACITY_ICON 1 #define MOUSE_OPACITY_OPAQUE 2 + +//world/proc/shelleo +#define SHELLEO_ERRORLEVEL 1 +#define SHELLEO_STDOUT 2 +#define SHELLEO_STDERR 3 diff --git a/code/__HELPERS/shell.dm b/code/__HELPERS/shell.dm new file mode 100644 index 000000000000..8b615eac0a02 --- /dev/null +++ b/code/__HELPERS/shell.dm @@ -0,0 +1,57 @@ +//Runs the command in the system's shell, returns a list of (error code, stdout, stderr) + +#define SHELLEO_NAME "data/shelleo." +#define SHELLEO_ERR ".err" +#define SHELLEO_OUT ".out" +/world/proc/shelleo(command) + var/static/list/shelleo_ids = list() + var/stdout = "" + var/stderr = "" + var/errorcode = 1 + var/shelleo_id + var/out_file = "" + var/err_file = "" + var/static/list/interpreters = list("[MS_WINDOWS]" = "cmd /c", "[UNIX]" = "sh -c") + var/interpreter = interpreters["[world.system_type]"] + if(interpreter) + for(var/seo_id in shelleo_ids) + if(!shelleo_ids[seo_id]) + shelleo_ids[seo_id] = TRUE + shelleo_id = "[seo_id]" + break + if(!shelleo_id) + shelleo_id = "[shelleo_ids.len + 1]" + shelleo_ids += shelleo_id + shelleo_ids[shelleo_id] = TRUE + out_file = "[SHELLEO_NAME][shelleo_id][SHELLEO_OUT]" + err_file = "[SHELLEO_NAME][shelleo_id][SHELLEO_ERR]" + errorcode = shell("[interpreter] \"[command]\" > [out_file] 2> [err_file]") + if(fexists(out_file)) + stdout = file2text(out_file) + fdel(out_file) + if(fexists(err_file)) + stderr = file2text(err_file) + fdel(err_file) + shelleo_ids[shelleo_id] = FALSE + else + CRASH("Operating System: [world.system_type] not supported") // If you encounter this error, you are encouraged to update this proc with support for the new operating system + . = list(errorcode, stdout, stderr) +#undef SHELLEO_NAME +#undef SHELLEO_ERR +#undef SHELLEO_OUT + +/proc/shell_url_scrub(url) + var/static/regex/bad_chars_regex = regex("\[^#%&./:=?\\w]*", "g") + var/scrubbed_url = "" + var/bad_match = "" + var/last_good = 1 + var/bad_chars = 1 + do + bad_chars = bad_chars_regex.Find(url) + scrubbed_url += copytext(url, last_good, bad_chars) + if(bad_chars) + bad_match = url_encode(bad_chars_regex.match) + scrubbed_url += bad_match + last_good = bad_chars + length(bad_match) + while(bad_chars) + . = scrubbed_url diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index cb971b617aa5..3e0773131605 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -15,7 +15,7 @@ GLOBAL_PROTECT(config_dir) return ..() /datum/configuration/vv_edit_var(var_name, var_value) - var/static/list/banned_edits = list("cross_address", "cross_allowed", "autoadmin", "autoadmin_rank") + var/static/list/banned_edits = list("cross_address", "cross_allowed", "autoadmin", "autoadmin_rank", "invoke_youtubedl") if(var_name in banned_edits) return FALSE return ..() @@ -93,6 +93,8 @@ GLOBAL_PROTECT(config_dir) var/panic_server_name var/panic_address //Reconnect a player this linked server if this server isn't accepting new players + var/invoke_youtubedl + //IP Intel vars var/ipintel_email var/ipintel_rating_bad = 1 @@ -475,6 +477,8 @@ GLOBAL_PROTECT(config_dir) if("panic_server_address") if(value != "byond://address:port") panic_address = value + if("invoke_youtubedl") + invoke_youtubedl = value if("show_irc_name") showircname = 1 if("see_own_notes") diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 5e013525de33..30e52ebbccae 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -261,6 +261,8 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list( verbs += GLOB.admin_verbs_poll if(rights & R_SOUNDS) verbs += GLOB.admin_verbs_sounds + if(config.invoke_youtubedl) + verbs += /client/proc/play_web_sound if(rights & R_SPAWN) verbs += GLOB.admin_verbs_spawn @@ -283,6 +285,7 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list( /client/proc/stealth, GLOB.admin_verbs_poll, GLOB.admin_verbs_sounds, + /client/proc/play_web_sound, GLOB.admin_verbs_spawn, /*Debug verbs added by "show debug verbs"*/ /client/proc/Cell, diff --git a/code/modules/admin/verbs/playsound.dm b/code/modules/admin/verbs/playsound.dm index 84a29d9c0119..20c1d91fd22f 100644 --- a/code/modules/admin/verbs/playsound.dm +++ b/code/modules/admin/verbs/playsound.dm @@ -36,7 +36,11 @@ for(var/mob/M in GLOB.player_list) if(M.client.prefs.toggles & SOUND_MIDI) + var/user_vol = M.client.chatOutput.adminMusicVolume + if(user_vol) + admin_sound.volume = vol * (user_vol / 100) SEND_SOUND(M, admin_sound) + admin_sound.volume = vol SSblackbox.add_details("admin_verb","Play Global Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! @@ -52,6 +56,62 @@ playsound(get_turf(src.mob), S, 50, 0, 0) SSblackbox.add_details("admin_verb","Play Local Sound") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! +/client/proc/play_web_sound() + set category = "Fun" + set name = "Play Internet Sound" + if(!check_rights(R_SOUNDS)) + return + + if(!config.invoke_youtubedl) + to_chat(src, "Youtube-dl was not configured, action unavailable") //Check config.txt for the INVOKE_YOUTUBEDL value + return + + var/web_sound_input = input("Enter content URL (supported sites only, leave blank to stop playing)", "Play Internet Sound via youtube-dl") as text|null + if(istext(web_sound_input)) + var/web_sound_url = "" + var/pitch + if(length(web_sound_input)) + + web_sound_input = trim(web_sound_input) + var/static/regex/html_protocol_regex = regex("https?://") + if(findtext(web_sound_input, ":") && !findtext(web_sound_input, html_protocol_regex)) + to_chat(src, "Non-http(s) URIs are not allowed.") + to_chat(src, "For youtube-dl shortcuts like ytsearch: please use the appropriate full url from the website.") + return + var/shell_scrubbed_input = shell_url_scrub(web_sound_input) + var/list/output = world.shelleo("[config.invoke_youtubedl] --format \"bestaudio\[ext=aac]/bestaudio\[ext=mp3]/bestaudio\[ext=m4a]\" --get-url \"[shell_scrubbed_input]\"") + var/errorlevel = output[SHELLEO_ERRORLEVEL] + var/stdout = output[SHELLEO_STDOUT] + var/stderr = output[SHELLEO_STDERR] + if(!errorlevel) + var/static/regex/content_url_regex = regex("https?://\\S+") + if(content_url_regex.Find(stdout)) + web_sound_url = content_url_regex.match + + if(SSevents.holidays && SSevents.holidays[APRIL_FOOLS]) + pitch = pick(0.5, 0.7, 0.8, 0.85, 0.9, 0.95, 1.1, 1.2, 1.4, 1.6, 2.0, 2.5) + to_chat(src, "You feel the Honkmother messing with your song...") + + log_admin("[key_name(src)] played web sound: [web_sound_input]") + message_admins("[key_name(src)] played web sound: [web_sound_input]") + else + to_chat(src, "Youtube-dl URL retrieval FAILED:") + to_chat(src, "[stderr]") + + else //pressed ok with blank + log_admin("[key_name(src)] stopped web sound") + message_admins("[key_name(src)] stopped web sound") + web_sound_url = " " + + if(web_sound_url) + for(var/m in GLOB.player_list) + var/mob/M = m + var/client/C = M.client + if((C.prefs.toggles & SOUND_MIDI) && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) + C.chatOutput.sendMusic(web_sound_url, pitch) + + SSblackbox.add_details("admin_verb","Play Internet Sound") + /client/proc/set_round_end_sound(S as sound) set category = "Fun" set name = "Set Round End Sound" diff --git a/code/modules/client/preferences_toggles.dm b/code/modules/client/preferences_toggles.dm index 1d04964a8b9b..8e8690bde325 100644 --- a/code/modules/client/preferences_toggles.dm +++ b/code/modules/client/preferences_toggles.dm @@ -144,6 +144,9 @@ TOGGLE_CHECKBOX(/datum/verbs/menu/Settings/Sound, togglemidis)() else to_chat(usr, "You will no longer hear sounds uploaded by admins") usr.stop_sound_channel(CHANNEL_ADMIN) + var/client/C = usr.client + if(C && C.chatOutput && !C.chatOutput.broken && C.chatOutput.loaded) + C.chatOutput.sendMusic(" ") SSblackbox.add_details("preferences_verb","Toggle Hearing Midis|[usr.client.prefs.toggles & SOUND_MIDI]") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /datum/verbs/menu/Settings/Sound/togglemidis/Get_checked(client/C) return C.prefs.toggles & SOUND_MIDI diff --git a/code/modules/goonchat/browserOutput.dm b/code/modules/goonchat/browserOutput.dm index cc44c47f4e53..4ed35b855b7e 100644 --- a/code/modules/goonchat/browserOutput.dm +++ b/code/modules/goonchat/browserOutput.dm @@ -13,6 +13,7 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("data/iconCache.sav")) //Cache of ic var/cookieSent = FALSE // Has the client sent a cookie for analysis var/broken = FALSE var/list/connectionHistory //Contains the connection history passed from chat cookie + var/adminMusicVolume = 100 //This is for the Play Global Sound verb /datum/chatOutput/New(client/C) owner = C @@ -79,6 +80,9 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("data/iconCache.sav")) //Cache of ic if("analyzeClientData") data = analyzeClientData(arglist(params)) + if("setMusicVolume") + data = setMusicVolume(arglist(params)) + if(data) ehjax_send(data = data) @@ -120,6 +124,16 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("data/iconCache.sav")) //Cache of ic data = json_encode(data) C << output("[data]", "[window]:ehjaxCallback") +/datum/chatOutput/proc/sendMusic(music, pitch) + var/list/music_data = list("adminMusic" = url_encode(url_encode(music))) + if(pitch) + music_data["musicRate"] = pitch + ehjax_send(data = music_data) + +/datum/chatOutput/proc/setMusicVolume(volume = "") + if(volume) + adminMusicVolume = Clamp(text2num(volume), 0, 100) + //Sends client connection details to the chat to handle and save /datum/chatOutput/proc/sendClientData() //Get dem deets diff --git a/code/modules/goonchat/browserassets/css/browserOutput.css b/code/modules/goonchat/browserassets/css/browserOutput.css index 3f6a5ac5ca28..98e4e6af9772 100644 --- a/code/modules/goonchat/browserassets/css/browserOutput.css +++ b/code/modules/goonchat/browserassets/css/browserOutput.css @@ -101,7 +101,7 @@ a.popt {text-decoration: none;} top: 0; right: 0; } -#options a { +#options .optionsCell { background: #ddd; height: 30px; padding: 5px 0; @@ -111,7 +111,7 @@ a.popt {text-decoration: none;} line-height: 28px; border-top: 1px solid #b4b4b4; } -#options a:hover {background: #ccc;} +#options .optionsCell:hover {background: #ccc;} #options .toggle { width: 40px; background: #ccc; @@ -121,7 +121,7 @@ a.popt {text-decoration: none;} } #options .sub {clear: both; display: none; width: 160px;} #options .sub.scroll {overflow-y: scroll;} -#options .sub a {padding: 3px 0 3px 8px; line-height: 30px; font-size: 0.9em; clear: both;} +#options .sub.optionsCell {padding: 3px 0 3px 8px; line-height: 30px; font-size: 0.9em; clear: both;} #options .sub span { display: block; line-height: 30px; @@ -136,6 +136,13 @@ a.popt {text-decoration: none;} line-height: 30px; float: right; } +#options .sub input { + position: absolute; + padding: 7px 5px; + width: 121px; + line-height: 30px; + float: left; +} #options .decreaseFont {border-top: 0;} /* POPUPS */ diff --git a/code/modules/goonchat/browserassets/html/browserOutput.html b/code/modules/goonchat/browserassets/html/browserOutput.html index f55e69ecd173..a9e30b6b5138 100644 --- a/code/modules/goonchat/browserassets/html/browserOutput.html +++ b/code/modules/goonchat/browserassets/html/browserOutput.html @@ -28,17 +28,19 @@ --ms
+