Adds Internet admin midi (#30457)

* Adds Internet admin midi

* Moves global regex into proc (shell_url_scrub)

* Moves shelleo to world
Adds quotes around the command in shelleo, to encapsulate it

* Admins can stop playing web sounds

* Revised internet midi extension order
Moved m4a to least preferred,
as it can potentially carry non-aac or non-mpeg-3 audio

* Kills \n in web sound

* play_web_sound availability based on config

* play web sound and shelleo tweaks

istypeless for in play web sound & readability parenthesis around binary AND
add return index defines for shelleo proc

* Security fix for play web sound
This commit is contained in:
JJRcop
2017-09-08 10:31:33 -04:00
committed by Jordan Brown
parent 3e6b94bd2a
commit 3259ac4c49
12 changed files with 246 additions and 17 deletions

View File

@@ -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

57
code/__HELPERS/shell.dm Normal file
View File

@@ -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

View File

@@ -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")

View File

@@ -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,

View File

@@ -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, "<span class='boldwarning'>Youtube-dl was not configured, action unavailable</span>") //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, "<span class='boldwarning'>Non-http(s) URIs are not allowed.</span>")
to_chat(src, "<span class='warning'>For youtube-dl shortcuts like ytsearch: please use the appropriate full url from the website.</span>")
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, "<span class='boldwarning'>Youtube-dl URL retrieval FAILED:</span>")
to_chat(src, "<span class='warning'>[stderr]</span>")
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"

View File

@@ -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

View File

@@ -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

View File

@@ -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 */

View File

@@ -28,17 +28,19 @@
<span class="ms" id="pingMs">--ms</span>
</div>
<div id="options">
<a href="#" class="toggle" id="toggleOptions" title="Options"><i class="icon-cog"></i></a>
<a href="#" class="optionsCell toggle" id="toggleOptions" title="Options"><i class="icon-cog"></i></a>
<div class="sub" id="subOptions">
<a href="#" class="decreaseFont" id="decreaseFont"><span>Decrease font size</span> <i class="icon-font">-</i></a>
<a href="#" class="increaseFont" id="increaseFont"><span>Increase font size</span> <i class="icon-font">+</i></a>
<a href="#" class="togglePing" id="togglePing"><span>Toggle ping display</span> <i class="icon-circle"></i></a>
<a href="#" class="highlightTerm" id="highlightTerm"><span>Highlight string</span> <i class="icon-tag"></i></a>
<a href="#" class="saveLog" id="saveLog"><span>Save chat log</span> <i class="icon-save"></i></a>
<a href="#" class="clearMessages" id="clearMessages"><span>Clear all messages</span> <i class="icon-eraser"></i></a>
<a href="#" class="optionsCell decreaseFont" id="decreaseFont"><span>Decrease font size</span> <i class="icon-font">-</i></a>
<a href="#" class="optionsCell increaseFont" id="increaseFont"><span>Increase font size</span> <i class="icon-font">+</i></a>
<a href="#" class="optionsCell togglePing" id="togglePing"><span>Toggle ping display</span> <i class="icon-circle"></i></a>
<a href="#" class="optionsCell highlightTerm" id="highlightTerm"><span>Highlight string</span> <i class="icon-tag"></i></a>
<a href="#" class="optionsCell saveLog" id="saveLog"><span>Save chat log</span> <i class="icon-save"></i></a>
<a href="#" class="optionsCell clearMessages" id="clearMessages"><span>Clear all messages</span> <i class="icon-eraser"></i></a>
<span class="optionsCell" id="musicVolumeSpan"><input type="range" class="hidden" id="musicVolume"><span id="musicVolumeText">Admin music volume</span><i class="icon-music"></i></span>
</div>
</div>
</div>
<audio class="hidden" id="adminMusic" autoplay></audio>
<script type="text/javascript" src="browserOutput.js"></script>
</body>
</html>

View File

@@ -61,8 +61,17 @@ var opts = {
'clientDataLimit': 5,
'clientData': [],
//Admin music volume update
'volumeUpdateDelay': 5000, //Time from when the volume updates to data being sent to the server
'volumeUpdating': false, //True if volume update function set to fire
'updatedVolume': 0, //The volume level that is sent to the server
};
function clamp(val, min, max) {
return Math.max(min, Math.min(val, max))
}
function outerHTML(el) {
var wrap = document.createElement('div');
wrap.appendChild(el.cloneNode(true));
@@ -95,6 +104,15 @@ function linkify(text) {
});
}
function byondDecode(message) {
// Basically we url_encode twice server side so we can manually read the encoded version and actually do UTF-8.
// The replace for + is because FOR SOME REASON, BYOND replaces spaces with a + instead of %20, and a plus with %2b.
// Marvelous.
message = message.replace(/\+/g, "%20");
message = decoder(message);
return message;
}
//Actually turns the highlight term match into appropriate html
function addHighlightMarkup(match) {
var extra = '';
@@ -176,11 +194,7 @@ function output(message, flag) {
if (flag !== 'internal')
opts.lastPang = Date.now();
// Basically we url_encode twice server side so we can manually read the encoded version and actually do UTF-8.
// The replace for + is because FOR SOME REASON, BYOND replaces spaces with a + instead of %20, and a plus with %2b.
// Marvelous.
message = message.replace(/\+/g, "%20")
message = decoder(message)
message = byondDecode(message)
//The behemoth of filter-code (for Admin message filters)
//Note: This is proooobably hella inefficient
@@ -423,6 +437,21 @@ function ehjaxCallback(data) {
var firebugEl = document.createElement('script');
firebugEl.src = 'https://getfirebug.com/firebug-lite-debug.js';
document.body.appendChild(firebugEl);
} else if (data.adminMusic) {
if (typeof data.adminMusic === 'string') {
var adminMusic = byondDecode(data.adminMusic);
adminMusic = adminMusic.match(/https?:\/\/\S+/) || '';
if (data.musicRate) {
var newRate = Number(data.musicRate);
if(newRate) {
$('#adminMusic').prop('defaultPlaybackRate', newRate);
}
} else {
$('#adminMusic').prop('defaultPlaybackRate', 1.0);
}
$('#adminMusic').prop('src', adminMusic);
$('#adminMusic').trigger("play");
}
}
}
}
@@ -446,6 +475,13 @@ function toggleWasd(state) {
opts.wasd = (state == 'on' ? true : false);
}
function sendVolumeUpdate() {
opts.volumeUpdating = false;
if(opts.updatedVolume) {
runByond('?_src_=chat&proc=setMusicVolume&param[volume]='+opts.updatedVolume);
}
}
/*****************************************
*
* DOM READY
@@ -486,6 +522,7 @@ $(function() {
'spingDisabled': getCookie('pingdisabled'),
'shighlightTerms': getCookie('highlightterms'),
'shighlightColor': getCookie('highlightcolor'),
'smusicVolume': getCookie('musicVolume'),
};
if (savedConfig.sfontSize) {
@@ -517,6 +554,14 @@ $(function() {
opts.highlightColor = savedConfig.shighlightColor;
internalOutput('<span class="internal boldnshit">Loaded highlight color of: '+savedConfig.shighlightColor+'</span>', 'internal');
}
if (savedConfig.smusicVolume) {
var newVolume = clamp(savedConfig.smusicVolume, 0, 100);
$('#adminMusic').prop('volume', newVolume / 100);
$('#musicVolume').val(newVolume);
opts.updatedVolume = newVolume;
sendVolumeUpdate();
internalOutput('<span class="internal boldnshit">Loaded music volume of: '+savedConfig.smusicVolume+'</span>', 'internal');
}
(function() {
var dataCookie = getCookie('connData');
@@ -835,6 +880,26 @@ $(function() {
opts.messageCount = 0;
});
$('#musicVolumeSpan').hover(function() {
$('#musicVolumeText').addClass('hidden');
$('#musicVolume').removeClass('hidden');
}, function() {
$('#musicVolume').addClass('hidden');
$('#musicVolumeText').removeClass('hidden');
});
$('#musicVolume').change(function() {
var newVolume = $('#musicVolume').val();
newVolume = clamp(newVolume, 0, 100);
$('#adminMusic').prop('volume', newVolume / 100);
setCookie('musicVolume', newVolume, 365);
opts.updatedVolume = newVolume;
if(!opts.volumeUpdating) {
setTimeout(sendVolumeUpdate, opts.volumeUpdateDelay);
opts.volumeUpdating = true;
}
});
$('img.icon').error(iconError);

View File

@@ -195,6 +195,14 @@ CHECK_RANDOMIZER
## Ban appeals URL - usually for a forum or wherever people should go to contact your admins.
# BANAPPEALS http://justanotherday.example.com
## System command that invokes youtube-dl, used by Play Internet Sound.
## You can install youtube-dl with
## "pip install youtube-dl" if you have pip installed
## from https://github.com/rg3/youtube-dl/releases
## or your package manager
## The default value assumes youtube-dl is in your system PATH
# INVOKE_YOUTUBEDL youtube-dl
## In-game features
##Toggle for having jobs load up from the .txt
# LOAD_JOBS_FROM_TXT

View File

@@ -93,6 +93,7 @@
#include "code\__HELPERS\pronouns.dm"
#include "code\__HELPERS\radio.dm"
#include "code\__HELPERS\sanitize_values.dm"
#include "code\__HELPERS\shell.dm"
#include "code\__HELPERS\text.dm"
#include "code\__HELPERS\time.dm"
#include "code\__HELPERS\type2type.dm"