From 89431aa34a8c01099dda72ca1f46b8c7ebcbb74d Mon Sep 17 00:00:00 2001 From: Joe Rayhawk Date: Tue, 13 Sep 2022 18:52:27 -0700 Subject: bungmobott: add transient OBS media source logic --- crystal/bungmobott.cr | 208 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 159 insertions(+), 49 deletions(-) diff --git a/crystal/bungmobott.cr b/crystal/bungmobott.cr index bbaac50..124d983 100755 --- a/crystal/bungmobott.cr +++ b/crystal/bungmobott.cr @@ -66,6 +66,14 @@ else aws = true end +# enable explosions? +if File.exists?( settings["configdir"] + "/explosionlist.txt" ) + explosions = Hash(String, String).new + File.each_line( settings["configdir"] + "/explosionlist.txt" ) do |line| + explosions[ line.downcase ] = line + end +end + client = Twitcr::Client.new( settings ) # derive channel_id from channel or vice versa @@ -137,7 +145,7 @@ snifflast = Int64.new(0) def urbandef( term : String ) #http://api.urbandictionary.com/v0/define?term=waifu client = HTTP::Client.new( "api.urbandictionary.com" ) - response = client.exec( "GET", "/v0/define?term=" + URI.encode_www_form( term ) ) + response = client.exec( "GET", "/v0/define?term=pawg" + URI.encode_www_form( term ) ) puts response.status_code json = JSON.parse( response.body ) return json["list"][0]["definition"].to_s.gsub( /[\[\]]/, "" ).gsub( /\n/, "" ) @@ -384,25 +392,6 @@ macro genrefuser2uid( path, uid, depth ) {% end %} end -macro temporaryduplicate( item ) - request = Hash( String, String | Bool ){ - "request-type" => "SetSceneItemProperties", - "message-id" => "SetSceneItemProperties", - "scene-name" => "meta", - "item" => {{item}}, - "visible" => true, - } - obsipc.send( request.to_json ) - request = Hash( String, String | Hash( String, String ) ){ - "request-type" => "DuplicateSceneItem", - "message-id" => "DuplicateSceneItem", - "item" => { "name" => {{item}} }, - "fromScene" => "meta" - } - ircipc.send( { "##{settings["channel"]}", "Duplicating source #{match[2]}" } ) - obsipc.send( request.to_json ) -end - voices = Hash(String, String).new if File.exists?( settings["configdir"] + "/voicelist.txt" ) File.each_line( settings["configdir"] + "/voicelist.txt" ) do |line| @@ -500,28 +489,29 @@ spawn do # Outgoing # state currentscene = "unknown" + canvas = { 1280, 720 } # scenename => { sourcename => { property => value } scenes = Hash( String, Hash( String, Hash( String, Int64 | Float64 | Bool | String ) ) ).new spawn do while msg = obsipc.receive pp msg - if ( match = msg.match( /^\x10source ([a-z0-9-_]+)/ ) ) - source = match[1] + if ( match = msg.match( /^\x10source(-notransition|) ([a-z0-9-_]+)/ ) ) + transition = ( match[1] != "-notransition" ) + source = match[2] sources = reversesource( scenes, currentscene ) pp sources request = Hash( String, String | Bool ).new request["request-type"] = "SetSceneItemProperties" request["message-id"] = "SetSceneItemProperties" - #request["request-type"] = "GetSceneItemProperties" unless ( scene = sources[source]? ) ircipc.send( { "#" + settings["channel"], "| obs: source not found: #{source}" } ) next end request["scene-name"] = scene request["item"] = source - if ( match = msg.match( /^\x10source ([a-z0-9-_]+) (true|false)/ ) ) + if ( match = msg.match( /^\x10source[-a-zA-Z0-9_]* ([a-z0-9-_]+) (true|false)/ ) ) request["visible"] = ( match[2] == "true" ) - elsif ( match = msg.match( /^\x10source ([a-z0-9-_]+)/ ) ) + elsif ( match = msg.match( /^\x10source[-a-zA-Z0-9_]* ([a-z0-9-_]+)/ ) ) pp scenes[scene][source]["render"] request["visible"] = ( scenes[scene][source]["render"]? != true ) # use a random similarly named source instead? @@ -537,24 +527,61 @@ spawn do end end obs_pubsub.send( request.to_json ) - #if scene == currentscene + puts "x10source: currentscene is " + currentscene + if transition request = Hash( String, String | Bool ).new request["request-type"] = "SetPreviewScene" request["message-id"] = "SetPreviewScene" request["scene-name"] = currentscene obs_pubsub.send( request.to_json ) + sleep 0.1 # somehow these get out-of-order otherwise? >:( request = Hash( String, String ).new request["request-type"] = "TransitionToProgram" request["message-id"] = "TransitionToProgram" obs_pubsub.send( request.to_json ) - - #end + end + elsif ( match = msg.match( /^\x10sceneitem ([a-z0-9-_]+)/ ) ) + source = match[1] + sources = reversesource( scenes, currentscene ) + pp sources + request = Hash( String, String | Bool ).new + request["request-type"] = "GetSceneItemProperties" + request["message-id"] = "GetSceneItemProperties" + unless ( scene = sources[source]? ) + ircipc.send( { "#" + settings["channel"], "| obs: source not found: #{source}" } ) + next + end + request["scene-name"] = scene + request["item"] = source + obs_pubsub.send( request.to_json ) elsif ( match = msg.match( /^\x10source/ ) ) request = Hash( String, String ){ "request-type" => "GetCurrentScene", "message-id" => "GetCurrentScene", } obs_pubsub.send( request.to_json ) + elsif ( match = msg.match( /^\x10newsource(-temporary|) ([a-zA-Z0-9-_]+)/ ) ) + temporary = ( match[1]? ) + + sourcename = "media#{temporary}-#{match[2]}" + + request = Hash( String, String | Hash( String, String | Bool ) ){ + "request-type" => "CreateSource", + "message-id" => "CreateSource", + "sourceName" => sourcename, + "sourceKind" => "ffmpeg_source", + "sceneName" => "meta-foreground", + #"setVisible" => "false", # this somehow triggers one or more MediaEnded events? + "sourceSettings" => { + "clear_on_media_end"=> true, + "close_when_inactive"=> false, + "is_local_file"=> true, + "local_file"=> "c:/cygwin64/home/user/effects/explosions/#{match[2]}.webm", + "looping"=> false + }, + } + ircipc.send( { "##{settings["channel"]}", "Creating #{sourcename}" } ) + obs_pubsub.send( request.to_json ) else obs_pubsub.send( msg ) end @@ -574,14 +601,15 @@ spawn do next # so skip them when "SwitchScenes" ircipc.send( { "#" + settings["channel"], "| obs: switched scene to " + ( json["scene-name"]?.as_s? || "unknown" ) } ) + puts "SwitchScenes: currentscene is " + currentscene currentscene = json["scene-name"].as_s - puts "currentscene is " + currentscene + puts "SwitchScenes: currentscene is " + currentscene when "MediaEnded" pp json source = json["sourceName"].as_s sources = reversesource( scenes, currentscene ) if scene = sources[source]? - if ( scenes[scene][source]["render"] == true ) && ( source =~ /^media-/ ) + if ( scenes[scene][source]["render"] == true ) && ( source =~ /^media-/ ) && ( source !~ /media-temporary/ ) request = Hash( String, String | Bool ){ "request-type" => "SetSceneItemProperties", "message-id" => "SetSceneItemProperties", @@ -593,18 +621,39 @@ spawn do obsipc.send( request.to_json ) end end -# If we want to start deleting temporary media sources? -# if ( ( source =~ /^media-/ ) && ( scene !~ /^meta/ ) ) -# request = Hash( String, String | Hash( String, String ) ){ -# "request-type" => "DeleteSceneItem", -# "message-id" => "DeleteSceneItem", -# "item" => { "name" => json["sourceName"].as_s }, -# } -# ircipc.send( { "##{settings["channel"]}", "Deleting #{source}" } ) -# obsipc.send( request.to_json ) -# end + if ( source =~ /^media-temporary-/ ) + request = Hash( String, String | Hash( String, String ) ){ + "scene" => "meta-foreground", + "request-type" => "DeleteSceneItem", + "message-id" => "DeleteSceneItem", + "item" => { "name" => json["sourceName"].as_s }, + } + ircipc.send( { "##{settings["channel"]}", "Deleting #{source}" } ) + obsipc.send( request.to_json ) + end when "SourceFilterVisibilityChanged" ircipc.send( { "##{settings["channel"]}", "| obs: source #{json["sourceName"]} filter #{json["filterName"]} visibility is currently #{json["filterEnabled"]}" } ) + when "SceneItemAdded" + request = Hash( String, String ){ + "request-type" => "GetCurrentScene", + "message-id" => "GetCurrentScene Quietly", + } + obsipc.send( request.to_json) + request = Hash( String, String ){ + "request-type" => "GetSceneList", + "message-id" => "GetSceneList Quietly", + } + obsipc.send( request.to_json ) + + if json["item-name"].as_s =~ /media-temporary/ + request = Hash( String, String | Bool ).new + request["request-type"] = "GetSceneItemProperties" + request["message-id"] = "GetSceneItemProperties" + request["scene-name"] = json["scene-name"].as_s + request["item"] = json["item-name"].as_s + obs_pubsub.send( request.to_json ) + end + #scenes[json["scene-name"].as_s][json["item-name"].as_s] when "SourceRenamed" sourceprev = json["previousName"].as_s sourcenew = json["newName"].as_s @@ -617,15 +666,41 @@ spawn do end } when "SceneItemVisibilityChanged" - scenes[json["scene-name"].as_s][json["item-name"].as_s]["render"] = json["item-visible"].as_bool - ircipc.send( { "##{settings["channel"]}", "| obs: source #{json["item-name"]} visibility is now #{json["item-visible"].as_bool}" } ) + print( json.to_pretty_json ) + if json["item-name"].as_s !~ /media-temporary/ + scenes[json["scene-name"].as_s][json["item-name"].as_s]["render"] = json["item-visible"].as_bool + ircipc.send( { "##{settings["channel"]}", "| obs: source #{json["item-name"]} visibility is now #{json["item-visible"].as_bool}" } ) + end end print( "RECEIVED: ") print( json.to_pretty_json ) print( "\n") case json["message-id"]?.as_s? + when "GetSceneItemProperties" + if json["name"].as_s =~ /media-temporary/ + size = { json["sourceWidth"].as_i, json["sourceHeight"].as_i } + request = Hash( String, String | Bool | Hash( String, Float64) ).new + request["request-type"] = "SetSceneItemProperties" + request["message-id"] = "SetSceneItemProperties" + request["scene-name"] = "meta-foreground" # why isn't this returned? + request["item"] = json["name"].as_s + # FIXME we're not getting real values for sourceWidth and sourceHeight until playback begins, but dropping frames would be horrible. + # We should probably just preprocess these values into explosionslist :/ + # ffprobe -show_entries stream=width,height big_explosion_1.webm + request["position"] = { + "x" => (rand(canvas[0]) - ( size[0] / 2 ) ).to_f , + "y" => (rand(canvas[1]) - ( size[1] / 2 ) ).to_f, + } + obsipc.send( request.to_json ) + #ircipc.send( { "##{settings["channel"]}", "| obs: #{request.to_json}" } ) + + end + when /^GetVideoInfo/ + canvas = { json["baseWidth"].as_i64, json["baseHeight"].as_i64 } + pp canvas when /^GetCurrentScene/ currentscene = json["name"].as_s + puts "GetCurrentScene: currentscene is " + currentscene if json["message-id"].as_s? !~ /Quietly/ sources = reversesource( scenes, currentscene ) ircipc.send( { "#" + settings["channel"], "| obs: " + sources.keys.join(", ") } ) @@ -644,10 +719,18 @@ spawn do source["type"].as_s? && ( scenes[scene["name"].as_s][source["name"].as_s]["type"] = source["type"].as_s ) source["x"].as_f? && ( scenes[scene["name"].as_s][source["name"].as_s]["x"] = source["x"].as_f ) source["y"].as_f? && ( scenes[scene["name"].as_s][source["name"].as_s]["y"] = source["y"].as_f ) - source["cx"].as_i64? && ( scenes[scene["name"].as_s][source["name"].as_s]["cx"] = source["cx"].as_i64 ) - source["cy"].as_i64? && ( scenes[scene["name"].as_s][source["name"].as_s]["cy"] = source["cy"].as_i64 ) source["source_cx"].as_f? && ( scenes[scene["name"].as_s][source["name"].as_s]["source_cx"] = source["source_cx"].as_f ) source["source_cy"].as_f? && ( scenes[scene["name"].as_s][source["name"].as_s]["source_cy"] = source["source_cy"].as_f ) + if source["cx"].as_i64? + scenes[scene["name"].as_s][source["name"].as_s]["cx"] = source["cx"].as_i64 + else + scenes[scene["name"].as_s][source["name"].as_s]["cx"] = 0.to_i64 + end + if source["cy"].as_i64? + scenes[scene["name"].as_s][source["name"].as_s]["cy"] = source["cy"].as_i64 + else + scenes[scene["name"].as_s][source["name"].as_s]["cy"] = 0.to_i64 + end } } pp scenes @@ -666,6 +749,12 @@ spawn do "message-id" => "GetSceneList Quietly", } obsipc.send( request.to_json) + sleep 1 + request = Hash( String, String ){ + "request-type" => "GetVideoInfo", + "message-id" => "GetVideoInfo Quietly", + } + obsipc.send( request.to_json) end obs_pubsub.run rescue ex : Socket::ConnectError @@ -777,7 +866,7 @@ loop do elsif ( ( cmd == "regeneratevoicelist" ) && ( own ) ) voices = regeneratevoicelist( settings, aws, gcloud ) ircipc.send( { "##{settings["channel"]}", "| Regenerated voicelist." } ) - elsif ( ( cmd =~ /^(voices|voicelist)$/ ) && ( mod || sub ) ) + elsif ( ( cmd =~ /^(voices|voice(s|)list|listvoice(s|))$/ ) && ( mod || sub ) ) bot.message( "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/voicelist.txt" ) elsif ( ( cmd =~ /^(setvoice|voice)$/ ) && ( mod || sub ) ) case message.params[1].split( " " ).size @@ -823,6 +912,12 @@ loop do else obsipc.send( "{ \"request-type\": \"GetSceneList\", \"message-id\": \"GetSceneList\" }" ) end + elsif ( ( cmd =~ /^sceneitem$/ ) && ( mod || own || vip ) ) + if ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ ) + obsipc.send( "\x10sceneitem #{match[2]}" ) + else + ircipc.send( { "##{settings["channel"]}", "Must provide provide a scene name as argument." } ) + end elsif ( ( cmd =~ /^source$/ ) && ( mod || own || vip ) ) request = Hash( String, String | Bool ).new if ( match[2]? ) && ( sourceargs = match[2].match( /^([a-zA-Z0-9-_]+) +(true|false)$/ ) ) @@ -854,14 +949,22 @@ loop do else ircipc.send( { "##{settings["channel"]}", "Must provide at least one source name as argument, and optionally one filter name and a value of true or false." } ) end - elsif ( cmd == "duplicate" && ( mod || own || vip ) ) - if ( match[2]? ) && ( filterargs = match[2].match( /^([a-zA-Z0-9-_]+)/ ) ) - temporaryduplicate( match[2] ) + elsif ( cmd == "create" && ( mod || own || vip ) ) + if ( match[2]? ) && ( match[2] =~ /^([a-zA-Z0-9-_]+)/ ) + obsipc.send( "\x10newsource-temporary #{match[2]}" ) else ircipc.send( { "##{settings["channel"]}", "Must provide at least one source name as argument." } ) end elsif ( cmd =~ /^(metaminute|youdied)$/ ) - obsipc.send( "\x10source media-#{cmd}" ) + obsipc.send( "\x10source-notransition media-#{cmd}" ) + elsif ( cmd =~ /^explosion$/ ) + if explosions + explosion = explosions.sample( 1 )[0][1] + puts explosion + obsipc.send( "\x10newsource-temporary #{explosion}" ) + else + puts "explosions undefined" + end # FIXME: This is only half-implemented elsif ( ( cmd =~ /^(user)$/ ) && ( mod || own ) ) if match[2]? && match[2] =~ /[0-9]+/ @@ -961,6 +1064,13 @@ loop do ircipc.send( { "##{settings["channel"]}", "| overlay requires an argument consisting wholly of lower case characters."} ) effectsmsg( settings, "overlay #{match[2]}" ) end + elsif ( cmd =~ /^(shout|shoutout)$/ ) + if match[2]? && match[2] =~ /^[a-z]+$/ + ircipc.send( { "##{settings["channel"]}", "| Go check out twitch.tv/#{match[2]}"} ) + effectsmsg( settings, "overlay gltext 5 Go follow #{match[2]}" ) + else + ircipc.send( { "##{settings["channel"]}", "| Missing argument."} ) + end elsif ( cmd =~ /^(system|dxdiag|computer)$/ ) && ( Crystal::DESCRIPTION =~ /linux/ ) ircipc.send( { "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/tmp/DxDiag.txt" } ) elsif ( cmd == "hackerman" ) -- cgit v1.2.3