summaryrefslogtreecommitdiff
path: root/crystal/bungmobott.cr
diff options
context:
space:
mode:
authorJoe Rayhawk <jrayhawk+git@omgwallhack.org>2022-10-29 16:56:39 -0700
committerJoe Rayhawk <jrayhawk+git@omgwallhack.org>2022-10-29 16:56:39 -0700
commit1fbe697f7ae040bfe7bd24925c805e7d5019d2ba (patch)
tree2a18a1da9f5ec3d45a4a1452fb32b155deed39e2 /crystal/bungmobott.cr
parent07adc920bbf28d0047a73fca3260d5eabce510d9 (diff)
downloadtwitchtools-1fbe697f7ae040bfe7bd24925c805e7d5019d2ba.tar.gz
twitchtools-1fbe697f7ae040bfe7bd24925c805e7d5019d2ba.zip
bungmobott.cr: irresponsible omnibus commit.
Finishes migration to OBSWebSocket. Fiberizes OBS events and chat better. Adds bungmoBoom effects.
Diffstat (limited to 'crystal/bungmobott.cr')
-rwxr-xr-xcrystal/bungmobott.cr1194
1 files changed, 371 insertions, 823 deletions
diff --git a/crystal/bungmobott.cr b/crystal/bungmobott.cr
index 1d91bf7..aad49b9 100755
--- a/crystal/bungmobott.cr
+++ b/crystal/bungmobott.cr
@@ -4,8 +4,10 @@ require "uri"
require "twitcr"
require "json"
require "crystal_mpd"
+require "obswebsocket"
STDOUT.sync = true
+STDOUT.flush_on_newline = true
struct Nil
def as_s?
@@ -67,8 +69,8 @@ else
end
# enable explosions?
+explosions = Hash( String, String ).new
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
@@ -105,12 +107,66 @@ else
settings["chat_user"] = settings["channel"]
end
-obs4ipc = Channel( String ).new
-obs5ipc = Channel( String ).new
+# fiber communication channels
ircipc = Channel( Tuple( String, String ) ).new
t2sipc = Channel( Tuple( String, String ) ).new
twitchipc = Channel( Tuple( String, Union( String, UInt64 ) ) ).new
+obs = OBSWebSocket::Server.new( "ws://127.0.0.1:4455/" )
+
+# OBS event thread
+spawn do
+ evchan = Channel( JSON::Any ).new
+ obs.scenes["meta-foreground"].to_h.each_key do | key |
+ if key =~ /^media-temporary-/
+ obs.inputs[key].delete!
+ end
+ end
+ obs.reqchan.send( { evchan, "subscribe events" } )
+ while json = evchan.receive
+ # A Fiber.yield occurs after this to make sure "json" doesn't get overwritten before we can use it.
+ spawn do
+ d = json # Copy *immediately*
+ case d["eventType"].as_s
+ when "CurrentProgramSceneChanged"
+ ircipc.send( { "#" + settings["channel"], "| obs: switched scene to " + ( d["eventData"]["sceneName"]?.as_s? || "unknown" ) } )
+ when "MediaInputPlaybackEnded"
+ if d["eventData"]["inputName"].as_s =~ /^media-temporary-/
+ obs.reqchan.send( { nil, OBSWebSocket.req( "RemoveInput", UUID.random.to_s, JSON.parse({ "inputName" => d["eventData"]["inputName"].as_s }.to_json) ) } )
+ end
+ when "SceneItemEnableStateChanged"
+ edata = d["eventData"]
+ name = obs.scenes[ edata["sceneName"].as_s ][ edata["sceneItemId"].as_i64 ].name
+ if name !~ /media-temporary/
+ ircipc.send( { "##{settings["channel"]}", "| obs: source #{name} visibility is now #{edata["sceneItemEnabled"].as_bool}" } )
+ end
+ when "SceneItemTransformChanged"
+ edata = d["eventData"]
+ sceneitem = obs.scenes[edata["sceneName"].as_s][edata["sceneItemId"].as_i64]
+ t = edata["sceneItemTransform"]
+ if ( sceneitem.name =~ /^media-temporary-/ )
+ spx = t["positionX" ].as_f.to_i64
+ spy = t["positionY" ].as_f.to_i64
+ sdx = t["sourceHeight"].as_f.to_i64
+ sdy = t["sourceWidth" ].as_f.to_i64
+ if ( spx == 0 && spy == 0 && sdx != 0 && sdy != 0 )
+ # source position randomizer
+ bx = obs.video.to_h["baseWidth" ].as(Int64 | Float64).to_i64
+ by = obs.video.to_h["baseHeight"].as(Int64 | Float64).to_i64
+ spx = ( rand(bx) - (sdx / 2) )
+ spy = ( rand(by) - (sdy / 2) )
+ sceneitem.transform( { "positionX" => spx, "positionY" => spy } )
+ end
+ end
+ when "SourceFilterEnableStateChanged"
+ edata = d["eventData"]
+ ircipc.send( { "##{settings["channel"]}", "| obs: source #{edata["sourceName"].as_s} filter #{edata["filterName"].as_s} visibility is currently #{edata["filterEnabled"].as_s}" } )
+ end
+ end
+ Fiber.yield
+ end
+end
+
# Twitch API request handling thread
spawn do
loop do
@@ -119,7 +175,7 @@ spawn do
cmd, arg = [ *twitchtuple ]
case cmd
when "get_user"
- userinfo = JSON.parse( client.get_user( arg.to_u64 ) )["data"][0]
+ userinfo = JSON.parse( client.get_user( arg ) )["data"][0]
pp userinfo
unless userinfo["broadcaster_type"].as_s.blank?
puts "\033[38;5;12m#{userinfo["login"]} is #{userinfo["broadcaster_type"]}\033[0m"
@@ -139,16 +195,31 @@ spawn do
end
end
-
-snifflast = Int64.new(0)
-
+def obstemporarymediacreate( obs : OBSWebSocket::Server, sname : String, iname, path : String )
+ iname = "media-temporary-explosion-#{iname}"
+ isettings = Hash( String, String | Bool | Int64 | Float64 ){
+ "advanced" => true,
+ "clear_on_media_end" => true,
+ "color_range" => 0.to_i64,
+ "is_local_file" => true,
+ "looping" => false,
+ "restart_on_activate" => true,
+ "local_file" => path,
+ }
+ response = obs.scenes[sname].createinput( iname, "ffmpeg_source", isettings )
+ # Skip ORM stuff and configure the SceneItem as fast as we possibly can
+ if ( rdata = response["responseData"]? ) && ( goodtransform = obs.sources.last_known_real_transform?( iname ) )
+ siid = rdata["sceneItemId"].as_i64
+ obs.reqchan.send( { nil, OBSWebSocket.req( "SetSceneItemTransform", UUID.random.to_s, JSON.parse( { "sceneName" => sname, "sceneItemId" => siid, "sceneItemTransform" => { "positionX" => goodtransform.to_h["positionX"], "positionY" => goodtransform.to_h["positionY"] } }.to_json ) ) } )
+ end
+end
def urbandef( term : String )
ssl_context = OpenSSL::SSL::Context::Client.new
{% if flag?(:windows) %}
ssl_context.verify_mode = OpenSSL::SSL::VerifyMode::NONE
{% end %}
- #http://api.urbandictionary.com/v0/define?term=waifu
+ #https://api.urbandictionary.com/v0/define?term=waifu
response = HTTP::Client.exec( "GET", "https://api.urbandictionary.com/v0/define?term=#{term}", tls: ssl_context )
puts response.status_code
json = JSON.parse( response.body )
@@ -293,29 +364,6 @@ def t2s( t2sipc : Channel, settings : Hash(String, String), userdir : String, ch
end
end
-def reversesource( scenes : Hash( String, Hash( String, Hash( String, Int64 | Float64 | Bool | String ) ) ), currentscene : String )
-# "meta-cam-front" => {
-# "vidcap-c920" => {
-# "render" => true, "type" => "dshow_input", "x" => 0.0, "y" => 0.0
-# },
-# "gst-bungfront" => {
-# "type" => "gstreamer-source", "x" => 0.0, "y" => 0.0
-# }
-# }
-# # scenes[currentscene][source]["type"] => "scene"
- sourcemap = Hash( String, String ).new
- scenes[currentscene].each do | source, sourcedata |
- # maybe make this recurse later
- sourcemap[source.to_s] = currentscene
- if ( ( ! sourcedata.has_key?("type") ) || ( sourcedata["type"] == "scene" ) )
- scenes[source.to_s].each do | metasource, metasourcedata |
- sourcemap[metasource.to_s] = source.to_s
- end
- end
- end
- return sourcemap
-end
-
def playaudiofile( filepath : String )
p = Process.new(
"powershell.exe",
@@ -485,505 +533,6 @@ spawn do
end
end
-def obsrequest( type : String, id : String, data : ( String | Nil | JSON::Any ) = nil )
- request = JSON.build do |json|
- json.object do
- json.field "op", 6
- json.field "d" do
- json.object do
- json.field "requestType", type
- json.field "requestId", id
- if data
- json.field "requestData", data
- end
- end
- end
- end
- end
- return request.to_s
-end
-
-# obs-websocket-5 thread
-spawn do
- loop do
- begin
-
- obs5_pubsub = HTTP::WebSocket.new( URI.parse( "ws://127.0.0.1:4455/" ), HTTP::Headers{"Cookie" => "SESSIONID=1235", "Sec-WebSocket-Protocol" => "obswebsocket.json"} )
- # Outgoing
- # state
- currentscene = "unknown"
- canvas = { 1280, 720 }
-
- # inputname => { property => value }
- inputs = Hash( String, Hash( String, String | Int64 | Float64 | Bool ) )
- # GetInputList
- # GetInputSettings
-
- # scenename => { sceneitemid => { property => value }
- scenes = Hash( String, Hash( String, Hash( String, Int64 | Float64 | Bool | String ) ) ).new
- # GetSceneList
- # GetSceneItemList
- # GetSceneItemTransform
-
- spawn do
- while msg = obs5ipc.receive
- pp msg
- if ( match = msg.match( /^\x10source(-notransition|) ([a-z0-9-_]+)/ ) )
- transition = ( match[1] != "-notransition" )
- source = match[2]
- sources = reversesource( scenes, currentscene )
- pp sources
- unless ( scene = sources[source]? )
- ircipc.send( { "#" + settings["channel"], "| obs5: source not found: #{source}" } )
- next
- end
- obs5_pubsub.send( obsrequest( "GetSceneItemEnabled", "GetSceneItemTransform", JSON.parse( { "sceneName" => scene, "sceneItemId" => scenes[scene][source]["id"] }.to_json ) ) )
- if ( match = msg.match( /^\x10source[-a-zA-Z0-9_]* ([a-z0-9-_]+) (true|false)/ ) )
- obs5_pubsub.send( obsrequest( "SetSceneItemEnabled", "SetSceneItemEnabled", JSON.parse( { "sceneName" => scene, "sceneItemId" => scenes[scene][source]["id"], "sceneItemEnabled" => ( match[2] == "true" ) }.to_json ) ) )
- elsif ( match = msg.match( /^\x10source[-a-zA-Z0-9_]* ([a-z0-9-_]+)/ ) )
- obs5_pubsub.send( obsrequest( "SetSceneItemEnabled", "SetSceneItemEnabled", JSON.parse( { "sceneName" => scene, "sceneItemId" => scenes[scene][source]["id"], "sceneItemEnabled" => ! scenes[scene][source]["render"] }.to_json ) ) )
- end
-# if transition
-# request = Hash( String, String | Bool ).new
-# request["request-type"] = "SetPreviewScene"
-# request["message-id"] = "SetPreviewScene"
-# request["scene-name"] = currentscene
-# obs4_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"
-# obs4_pubsub.send( request.to_json )
-# end
- elsif ( match = msg.match( /^\x10source/ ) )
- pp scenes
- pp currentscene
- sources = reversesource( scenes, currentscene )
- #ircipc.send( { "#" + settings["channel"], "| obs: " + sources.keys.join(", ") } )
- obs5_pubsub.send( obsrequest( "GetSceneItemList", "GetSceneItemList", JSON.parse( { "sceneName" => currentscene }.to_json ) ) )
- else
- obs5_pubsub.send( msg )
- end
- end
- end
- # Incoming
- obs5_pubsub.on_message do | message |
- json = JSON.parse( message )
- if json["error"]?
- puts json["error"]
- ircipc.send( { "#" + settings["channel"], "| obs5: #{json["error"]}" } )
- next
- end
- case json["op"].as_i
- when 0 # hello
- json_text = %({"op":1,"d":{"rpcVersion":1}})
- obs5_pubsub.send( json_text )
- when 5 # event
- edata = json["d"]["eventData"]
- case json["d"]["eventType"].as_s
- when "CurrentProgramSceneChanged"
- ircipc.send( { "#" + settings["channel"], "| obs5: switched scene to " + ( edata["sceneName"]?.as_s? || "unknown" ) } )
- puts "SwitchScenes: currentscene is " + currentscene
- currentscene = edata["sceneName"].as_s
- puts "SwitchScenes: currentscene is " + currentscene
- when "SceneItemEnableStateChanged"
- # FIXME: No sceneItemName? need to convert scenes/sceneitems into a proper object interface or build a new index so we can lookup name by id
- edata["sceneItemEnabled"].as_bool
- edata["sceneItemId"].as_i
- edata["sceneName"].as_s
- end
- when 7 # requestResponse
- case json["d"]["requestType"].as_s
- when "GetStats" # FIXME: RequestBatch this with GetOutputStatus?
- streamstatus = json["d"]["responseData"].as_h
-
- obs5_pubsub.send( obsrequest( "GetOutputList", "GetOutputList" ))
- when "GetOutputList"
- output_name = String.new
- json["d"]["responseData"]["outputs"].as_a.each do | output |
- if output["outputKind"].as_s == "rtmp_output"
- output_name = output["outputName"].as_s
- end
- end
- unless output_name.empty?
- obs5_pubsub.send( obsrequest( "GetOutputStatus", "GetOutputStatus", JSON.parse( { "outputName" => output_name }.to_json ) ))
- end
- when "GetOutputStatus"
- streamstatus = streamstatus.merge( json["d"]["responseData"].as_h )
- ircipc.send( { "##{settings["channel"]}", "| #{streamstatus["outputTimecode"].as_s[0..7]} #{streamstatus["activeFps"].as_f.to_s[0,2]}fps Usage: #{streamstatus["cpuUsage"].as_f.to_i64}% #{streamstatus["memoryUsage"].as_f.to_i64}MiB Frame losses: #{streamstatus["outputSkippedFrames"].as_i64} #{streamstatus["outputSkippedFrames"].as_i64}" } )
- when /^GetSceneItemList/
- pp json
- rdata = json["d"]["responseData"]
- ( match = json["d"]["requestId"].as_s.match( /^GetSceneItemList-([A-Za-z0-9-_\/]+)/ ) ) || next
- # scenename => { sourcename => { property => value }
- # scenes = Hash( String, Hash( String, Hash( String, Int64 | Float64 | Bool | String ) ) ).new
- rdata["sceneItems"].as_a.each do | source |
-
- scenes[match[1]][source["sourceName"]]? || ( scenes[match[1]][source["sourceName"].as_s] = Hash( String, Int64 | Float64 | Bool | String ).new )
- source["sceneItemEnabled"].as_bool != Nil && ( scenes[match[1]][source["sourceName"].as_s]["render"] = source["sceneItemEnabled"].as_bool )
- source["sceneItemId"].as_i64? && ( scenes[match[1]][source["sourceName"].as_s]["id"] = source["sceneItemId"].as_i64 )
- source["inputKind"].as_s? && ( scenes[match[1]][source["sourceName"].as_s]["type"] = source["inputKind"].as_s )
- source["sceneItemTransform"]["positionX"].as_f? && ( scenes[match[1]][source["sourceName"].as_s]["x"] = source["sceneItemTransform"]["positionX"].as_f )
- source["sceneItemTransform"]["positionY"].as_f? && ( scenes[match[1]][source["sourceName"].as_s]["y"] = source["sceneItemTransform"]["positionY"].as_f )
- source["sceneItemTransform"]["sourceWidth"].as_f? && ( scenes[match[1]][source["sourceName"].as_s]["source_cx"] = source["sceneItemTransform"]["sourceWidth"].as_f )
- source["sceneItemTransform"]["sourceHeight"].as_f? && ( scenes[match[1]][source["sourceName"].as_s]["source_cy"] = source["sceneItemTransform"]["sourceHeight"].as_f )
- if source["sceneItemTransform"]["width"].as_i64?
- scenes[match[1]][source["sourceName"].as_s]["cx"] = source["sceneItemTransform"]["cx"].as_i64
- else
- scenes[match[1]][source["sourceName"].as_s]["cx"] = 0.to_i64
- end
- if source["sceneItemTransform"]["height"].as_i64?
- scenes[match[1]][source["sourceName"].as_s]["cy"] = source["sceneItemTransform"]["cy"].as_i64
- else
- scenes[match[1]][source["sourceName"].as_s]["cy"] = 0.to_i64
- end
- end
- when "GetSceneList"
- rdata = json["d"]["responseData"]
- currentscene = rdata["currentProgramSceneName"].as_s
- pp rdata["scenes"]
- rdata["scenes"].as_a.each do |scene|
- scenes[scene["sceneName"].as_s] = Hash( String, Hash( String, Int64 | Float64 | Bool | String ) ).new
- end
- if json["d"]["requestId"].as_s =~ /Quietly/ # We need to populate the scenes table once at startup
- scenes.keys.each do |scene|
- obs5_pubsub.send( obsrequest( "GetSceneItemList", "GetSceneItemList-#{scene}", JSON.parse( { "sceneName" => scene }.to_json ) ))
- end
- else # Loudly
- ircipc.send( { "#" + settings["channel"], "| obs5: " + rdata["scenes"].as_a.map{ |scene| scene["sceneName"] }.join(", ") } )
- end
- when "GetVideoSettings"
- rdata = json["d"]["responseData"]
- canvas = { rdata["baseWidth"].as_i.to_u64 , rdata["baseHeight"].as_i.to_u64 }
- pp canvas
- end
- end
- print( "5 RECEIVED: ")
- print( json.to_pretty_json )
- print( "\n")
- end
- spawn do
- # authentication
- sleep 1
- obs5_pubsub.send( obsrequest( "GetVideoSettings", "GetVideoSettings" ))
- obs5_pubsub.send( obsrequest( "GetStats", "GetStats" ))
- sleep 1
- obs5_pubsub.send( obsrequest( "GetSceneList", "GetSceneList Quietly" ))
- end
- obs5_pubsub.run
- rescue ex : Socket::ConnectError
- # these are a bit noisy
- #pp ex
- obs5_pubsub && obs5_pubsub.close
- rescue ex : IO::Error
- pp ex
- ircipc.send( { "#" + settings["channel"], "obs.cr:obs: " + ex.to_s.gsub(/\r|\n/, ' ') + ": Maybe try again in 10 seconds?" } )
- rescue ex
- pp ex
- pp ex.backtrace?
- ircipc.send( { "#" + settings["channel"], "irc.cr:obs: " + ex.to_s.gsub(/\r|\n/, ' ') } )
- obs5_pubsub && obs5_pubsub.close
- end
- sleep 10
- next
- end
-end
-
-# obs-websocket-4 thread
-spawn do
- loop do
- begin
-
- obs4_pubsub = HTTP::WebSocket.new( URI.parse( "ws://127.0.0.1:4444/" ), HTTP::Headers{"Cookie" => "SESSIONID=1234"} )
- # 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 = obs4ipc.receive
- pp msg
- 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"
- unless ( scene = sources[source]? )
- ircipc.send( { "#" + settings["channel"], "| obs4: source not found: #{source}" } )
- next
- end
- request["scene-name"] = scene
- request["item"] = source
- 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-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?
- randsources = sources.keys.select!{ |x| x =~ /^#{source}/}
- if ( randsources.size > 1 )
- #if Random.new.next_bool # 50/50
- if Random.new.rand(5) == 0 # 20/80
- source = randsources.sample
- scene = sources[source]
- request["scene-name"] = scene
- request["item"] = source
- end
- end
- end
- obs4_pubsub.send( request.to_json )
- 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
- obs4_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"
- obs4_pubsub.send( request.to_json )
- 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"], "| obs4: source not found: #{source}" } )
- next
- end
- request["scene-name"] = scene
- request["item"] = source
- obs4_pubsub.send( request.to_json )
- elsif ( match = msg.match( /^\x10source/ ) )
- request = Hash( String, String ){
- "request-type" => "GetCurrentScene",
- "message-id" => "GetCurrentScene",
- }
- obs4_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}" } )
- obs4_pubsub.send( request.to_json )
- else
- obs4_pubsub.send( msg )
- end
- end
- end
- # Incoming
- obs4_pubsub.on_message do | message |
- json = JSON.parse( message )
- if json["error"]?
- puts json["error"]
- ircipc.send( { "#" + settings["channel"], "| obs4: #{json["error"]}" } )
- next
- end
- case json["update-type"]?
- when "StreamStatus" # these are a bit noisy,
- streamstatus = json.as_h
- next # so skip them
- when "SwitchScenes"
- ircipc.send( { "#" + settings["channel"], "| obs4: switched scene to " + ( json["scene-name"]?.as_s? || "unknown" ) } )
- puts "SwitchScenes: currentscene is " + currentscene
- currentscene = json["scene-name"].as_s
- 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-/ ) && ( source !~ /media-temporary/ )
- request = Hash( String, String | Bool ){
- "request-type" => "SetSceneItemProperties",
- "message-id" => "SetSceneItemProperties",
- "scene-name" => scene,
- "item" => source,
- "visible" => false
- }
- ircipc.send( { "##{settings["channel"]}", "| obs4: Media ended; disabling #{source}" } )
- obs4ipc.send( request.to_json )
- end
- 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}" } )
- obs4ipc.send( request.to_json )
- end
- when "SourceFilterVisibilityChanged"
- ircipc.send( { "##{settings["channel"]}", "| obs4: source #{json["sourceName"]} filter #{json["filterName"]} visibility is currently #{json["filterEnabled"]}" } )
- when "SceneItemAdded"
- request = Hash( String, String ){
- "request-type" => "GetCurrentScene",
- "message-id" => "GetCurrentScene Quietly",
- }
- obs4ipc.send( request.to_json)
- request = Hash( String, String ){
- "request-type" => "GetSceneList",
- "message-id" => "GetSceneList Quietly",
- }
- obs4ipc.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
- obs4_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
- # does not tell us the scenes involved?
- # Will have to iterate through them.
- scenes.keys.each{ | scene |
- if scenes[scene][sourceprev]?
- scenes[scene][sourcenew] = scenes[scene][sourceprev]
- scenes[scene].delete(sourceprev)
- end
- }
- when "SceneItemVisibilityChanged"
- 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"]}", "| obs4: source #{json["item-name"]} visibility is now #{json["item-visible"].as_bool}" } )
- end
- end
- print( "4 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,
- }
- obs4ipc.send( request.to_json )
- #ircipc.send( { "##{settings["channel"]}", "| obs4: #{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"], "| obs4: " + sources.keys.join(", ") } )
- end
- when "GetSourceFilters"
- ircipc.send( { "#" + settings["channel"], "| obs4: " + json["filters"].as_a.map{ |filter| "#{filter["name"].as_s}: #{filter["enabled"].as_bool}" }.join(", ") } )
- when /^GetSceneList/
- if json["message-id"].as_s? !~ /Quietly/
- ircipc.send( { "#" + settings["channel"], "| obs4: " + json["scenes"].as_a.map{ |scene| scene["name"] }.join(", ") } )
- end
- json["scenes"].as_a.each{ |scene|
- scenes[scene["name"].as_s]? || ( scenes[scene["name"].as_s] = Hash( String, Hash( String, Int64 | Float64 | Bool | String ) ).new )
- scene["sources"].as_a.each{ |source|
- scenes[scene["name"].as_s][source["name"]]? || ( scenes[scene["name"].as_s][source["name"].as_s] = Hash( String, Int64 | Float64 | Bool | String ).new )
- source["render"].as_bool != Nil && ( scenes[scene["name"].as_s][source["name"].as_s]["render"] = source["render"].as_bool )
- 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["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
- end
- end
- spawn do
- sleep 1
-
- request = Hash( String, String ){
- "request-type" => "GetCurrentScene",
- "message-id" => "GetCurrentScene Quietly",
- }
- obs4ipc.send( request.to_json )
- request = Hash( String, String ){
- "request-type" => "GetSceneList",
- "message-id" => "GetSceneList Quietly",
- }
- obs4ipc.send( request.to_json)
- sleep 1
- request = Hash( String, String ){
- "request-type" => "GetVideoInfo",
- "message-id" => "GetVideoInfo Quietly",
- }
- obs4ipc.send( request.to_json)
- end
- obs4_pubsub.run
- rescue ex : Socket::ConnectError
- # these are a bit noisy
- #pp ex
- obs4_pubsub && obs4_pubsub.close
- rescue ex : IO::Error
- pp ex
- ircipc.send( { "#" + settings["channel"], "obs.cr:obs4: " + ex.to_s.gsub(/\r|\n/, ' ') + ": Maybe try again in 10 seconds?" } )
- rescue ex
- pp ex
- pp ex.backtrace?
- ircipc.send( { "#" + settings["channel"], "irc.cr:obs4: " + ex.to_s.gsub(/\r|\n/, ' ') } )
- obs4_pubsub && obs4_pubsub.close
- end
- sleep 10
- next
- end
-end
-
# main thread: IRC
loop do
begin
@@ -1004,182 +553,187 @@ loop do
# Create a handler to process incoming messages
bot.on_message do |message|
spawn do
- next unless ( userlogreturn = userlog( ircipc, settings, message ) )
- chatuser, uid, userdir, lastseen = userlogreturn
- if ( t2sreturn = t2s( t2sipc, settings, userdir, chatuser, message.params[1] ) )
- lastvoice.insert( 0, t2sreturn )
- lastvoice = lastvoice[0..4]
- end
- # Have we seen this user lately?
- if ( ( Time.utc.to_unix - lastseen ) >= 14400 )
- twitchipc.send( { "get_user", uid.to_u64 } )
- twitchipc.send( { "get_followers", uid.to_u64 } )
- prevnames = Array( String ).new
- if ( prevnames = Dir.children( settings["statedir"] + "/uids/" + uid + "/names" ) ) && ( prevnames.size > 1 )
- prevnames.delete( chatuser )
- puts "\033[38;5;14m#{chatuser} previous names: #{prevnames.join(", ")}\033[0m"
- end
- # play random fanfare if available.
- {% if flag?(:windows) %}
- # This file hierarchy gets manually set up for now.
- # Maybe someday let mods do something like:
- # !fanfare add pipne https://pip.ne/sniff.mp3
- if File.exists?( userdir + "/fanfare/" )
- playaudiofile( userdir + "/fanfare/" + Dir.children( userdir + "/fanfare/" ).sample )
+ next unless ( userlogreturn = userlog( ircipc, settings, message ) )
+ chatuser, uid, userdir, lastseen = userlogreturn
+ if ( t2sreturn = t2s( t2sipc, settings, userdir, chatuser, message.params[1] ) )
+ lastvoice.insert( 0, t2sreturn )
+ lastvoice = lastvoice[0..4]
+ end
+ # Have we seen this user lately?
+ if ( ( Time.utc.to_unix - lastseen ) >= 14400 )
+ twitchipc.send( { "get_user", uid.to_u64 } )
+ twitchipc.send( { "get_followers", uid.to_u64 } )
+ prevnames = Array( String ).new
+ if ( prevnames = Dir.children( settings["statedir"] + "/uids/" + uid + "/names" ) ) && ( prevnames.size > 1 )
+ prevnames.delete( chatuser )
+ puts "\033[38;5;14m#{chatuser} previous names: #{prevnames.join(", ")}\033[0m"
end
- {% else %}
- t2smsg( settings, "fanfare #{uid} #{chatuser}" )
- {% end %}
- end
- next unless ( ( match = message.params[1].match(/^ *!([A-Za-z]+) (([a-zA-Z0-9= _\:,.&'\/?;\\\(\)\[\]+\-]|!)+)/) || message.params[1].match(/^ *!([A-Za-z]+)/) ) )
- cmd = match[1]
- own = ( message.tags["room-id"] == message.tags["user-id"] )
- vip = ( message.tags["badges"].to_s.matches?( /(^|,)vip\// ) )
- mod = ( message.tags["mod"] == "1" )
- sub = ( message.tags["subscriber"] == "1" )
- if ( ( cmd =~ /^(substitute|voicesub)$/ ) && ( mod || sub ) )
- case message.params[1].split( " " ).size
- when 1
- if File.exists?( userdir + "/voicesub" )
- bot.message( "##{settings["channel"]}", "| Current name substitution is \"#{File.read( userdir + "/voicesub" )}\"." )
- else
- bot.message( "##{settings["channel"]}", "| Current name substitution is disabled." )
+ # play random fanfare if available.
+ {% if flag?(:windows) %}
+ # This file hierarchy gets manually set up for now.
+ # Maybe someday let mods do something like:
+ # !fanfare add pipne https://pip.ne/sniff.mp3
+ if File.exists?( userdir + "/fanfare/" )
+ playaudiofile( userdir + "/fanfare/" + Dir.children( userdir + "/fanfare/" ).sample )
+ end
+ {% else %}
+ t2smsg( settings, "fanfare #{uid} #{chatuser}" )
+ {% end %}
+ end
+ own = ( message.tags["room-id"] == message.tags["user-id"] )
+ vip = ( message.tags["badges"].to_s.matches?( /(^|,)vip\// ) )
+ mod = ( message.tags["mod"] == "1" )
+ sub = ( message.tags["subscriber"] == "1" )
+ # bungmoBoom
+ # message.tags["emotes"] =~ /^322820/
+ bungmobooms = Array( String ).new
+ if ( message.tags["emotes"]? ) &&
+ ( message.tags["emotes"] ) &&
+ ( bungmobooms = message.tags["emotes"].not_nil!.split("/").select( /^322820[:_]/ ).join(",").split(",") ) &&
+ ( ! bungmobooms[0].empty? ) &&
+ ( explosions.size > 0 )
+ explosions.values.sample( bungmobooms.size ).each do | explosion |
+ obstemporarymediacreate( obs, "meta-foreground", explosion, "C:/cygwin64/home/user/effects/explosions/#{explosion}.webm" )
end
- else
- if match[2]?
- voicesub = match[2].downcase
- if voicesub =~ /^(disabled|null|disable|none)$/
- if File.exists?( userdir + "/voicesub" )
- File.delete( userdir + "/voicesub" )
- bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is now disabled." )
+ end
+ next unless ( ( match = message.params[1].match(/^ *!([A-Za-z]+) (([a-zA-Z0-9= _\:,.&'\/?;\\\(\)\[\]+\-]|!)+)/) || message.params[1].match(/^ *!([A-Za-z]+)/) ) )
+ cmd = match[1]
+ if ( ( cmd =~ /^(substitute|voicesub)$/ ) && ( mod || sub ) )
+ case message.params[1].split( " " ).size
+ when 1
+ if File.exists?( userdir + "/voicesub" )
+ bot.message( "##{settings["channel"]}", "| Current name substitution is \"#{File.read( userdir + "/voicesub" )}\"." )
+ else
+ bot.message( "##{settings["channel"]}", "| Current name substitution is disabled." )
+ end
+ else
+ if match[2]?
+ voicesub = match[2].downcase
+ if voicesub =~ /^(disabled|null|disable|none)$/
+ if File.exists?( userdir + "/voicesub" )
+ File.delete( userdir + "/voicesub" )
+ bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is now disabled." )
+ else
+ bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is already disabled." )
+ end
else
- bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is already disabled." )
+ Dir.mkdir_p( userdir )
+ File.write( userdir + "/voicesub", voicesub )
+ bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is now \"#{File.read( userdir + "/voicesub" )}\"." )
end
- else
- Dir.mkdir_p( userdir )
- File.write( userdir + "/voicesub", voicesub )
- bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is now \"#{File.read( userdir + "/voicesub" )}\"." )
end
end
- end
- elsif ( cmd =~ /^(commands|help)$/ )
- bot.message( "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/txt/commands.txt" )
- elsif ( cmd =~ /^(dexem)$/ )
- ircipc.send( { "##{settings["channel"]}", "| You're doing great work, Dexem!" } )
- elsif ( ( cmd == "lastvoice" ) && ( mod || sub ) )
- unless lastvoice.empty?
- bot.message( "##{settings["channel"]}", "| Last voices were " + lastvoice.join( ", " ) )
- else
- bot.message( "##{settings["channel"]}", "| No voices used so far." )
- end
- elsif ( ( cmd == "regeneratevoicelist" ) && ( own ) )
- voices = regeneratevoicelist( settings, aws, gcloud )
- ircipc.send( { "##{settings["channel"]}", "| Regenerated voicelist." } )
- 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
- when 1
- namesub, voice_setting, voice_output = getvoice( settings, userdir, chatuser )
- bot.message( "##{settings["channel"]}", "| Current voice is #{voice_setting}" )
- when 2
- if match[2]?
- voice = match[2].downcase
- if voice =~ /disabled|null|disable|none|random/
- if File.exists?( userdir + "/voice" )
- File.delete( userdir + "/voice" )
- bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is now random." )
+ elsif ( cmd =~ /^(commands|help)$/ )
+ bot.message( "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/txt/commands.txt" )
+ elsif ( cmd =~ /^(dexem)$/ )
+ ircipc.send( { "##{settings["channel"]}", "| You're doing great work, Dexem!" } )
+ elsif ( ( cmd == "lastvoice" ) && ( mod || sub ) )
+ unless lastvoice.empty?
+ bot.message( "##{settings["channel"]}", "| Last voices were " + lastvoice.join( ", " ) )
+ else
+ bot.message( "##{settings["channel"]}", "| No voices used so far." )
+ end
+ elsif ( ( cmd == "regeneratevoicelist" ) && ( own ) )
+ voices = regeneratevoicelist( settings, aws, gcloud )
+ ircipc.send( { "##{settings["channel"]}", "| Regenerated voicelist." } )
+ 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
+ when 1
+ namesub, voice_setting, voice_output = getvoice( settings, userdir, chatuser )
+ bot.message( "##{settings["channel"]}", "| Current voice is #{voice_setting}" )
+ when 2
+ if match[2]?
+ voice = match[2].downcase
+ if voice =~ /disabled|null|disable|none|random/
+ if File.exists?( userdir + "/voice" )
+ File.delete( userdir + "/voice" )
+ bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is now random." )
+ else
+ bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is already random." )
+ end
+ elsif voices.has_key?( voice )
+ csvoice = voices[voice]
+ Dir.mkdir_p( userdir )
+ File.write( userdir + "/voice", csvoice )
+ pp userdir
+ bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is now #{File.read( userdir + "/voice" )}." )
else
- bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is already random." )
+ pp ( match )
+ bot.message( "##{settings["channel"]}", "| Invalid voice. To see list, use !voices" )
end
- elsif voices.has_key?( voice )
- csvoice = voices[voice]
- Dir.mkdir_p( userdir )
- File.write( userdir + "/voice", csvoice )
- pp userdir
- bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is now #{File.read( userdir + "/voice" )}." )
- # TODO: make separate script to print streamelements URLs to client machine
+ end
+ when 3
+ else
+ end
+ elsif ( cmd =~ /^uptime$/ )
+ stats = obs.stats.to_h
+ ostatus = obs.outputs["adv_stream"].status.to_h
+ ircipc.send( { "##{settings["channel"]}", "| #{ostatus["outputTimecode"].to_s[0..7]} #{stats["activeFps"].to_s[0,2]}fps Usage: #{stats["cpuUsage"].as(Float64).to_i64}% #{stats["memoryUsage"].as(Float64).to_i64}MiB Frame losses: #{stats["outputSkippedFrames"].as(Int64)} #{stats["renderSkippedFrames"].as(Int64)}" } )
+ # !newimage [name] [url] (random|top|bottom|left|right|center)
+ # download url, verify mimetype with libmagic, duplicate existing medialoop source, change "file" SourceSetting
+ elsif ( ( cmd =~ /^inputsetting(|s)$/ ) && ( mod || own || vip ) )
+ if ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ )
+ ircipc.send( { "##{settings["channel"]}", "| #{obs.inputs[match[2]].settings.to_h.to_s} " } )
+ end
+ elsif ( ( cmd =~ /^input(|s)$/ ) && ( mod || own || vip ) )
+ ircipc.send( { "##{settings["channel"]}", "| inputs: #{obs.inputs.to_h.keys.join(" ")}" } )
+ elsif ( ( cmd =~ /^scene(|s)$/ ) && ( mod || own || vip ) )
+ if ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ )
+ obs.scenes[match[2]].program!
+ else
+ ircipc.send( { "##{settings["channel"]}", "| scenes: #{obs.scenes.to_h.keys.join(" ")}" } )
+ end
+ elsif ( ( cmd =~ /^source(|s)$/ ) && ( mod || own || vip ) )
+ request = Hash( String, String | Bool ).new
+ if ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ )
+ obs.scenes.current.metascene[match[2]].toggle!
+ # in studio mode, direct Scene->SceneItem toggles require a transition
+ obs.scenes.current.preview!
+ obs.transition!
+ else
+ ircipc.send( { "##{settings["channel"]}", "| current sources: #{obs.scenes.current.metascene.keys.join(" ")}" } )
+ end
+ elsif ( ( cmd =~ /^filter(|s)$/ ) && ( mod || own ) )
+ if ( match[2]? ) && ( filterargs = match[2].match( /^([a-zA-Z0-9-_]+) +([a-zA-Z0-9-_]+)/ ) )
+ obs.sources.to_h[filterargs[1]].filters[filterargs[2]].toggle!
+ elsif ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ )
+ ircipc.send( { "##{settings["channel"]}", "| #{match[2]} filters: #{obs.sources.to_h[match[2]].filters.to_h.keys.join(" ")}" } )
+ else
+ ircipc.send( { "##{settings["channel"]}", "Must provide at least one source name as argument, and optionally one filter name to toggle on or off." } )
+ end
+ elsif ( cmd == "create" && ( mod || own || vip ) )
+ if ( match[2]? ) && ( match[2] =~ /^([a-zA-Z0-9-_]+)/ )
+ obstemporarymediacreate( obs, "meta-foreground", match[2], "C:/cygwin64/home/user/effects/explosions/#{match[2]}.webm" )
+ else
+ ircipc.send( { "##{settings["channel"]}", "Must provide at least one source name as argument." } )
+ end
+ elsif ( cmd =~ /^(metaminute|youdied)$/ )
+ obs.scenes.current.metascene["media-#{cmd}"].enable!
+ elsif ( cmd =~ /^explosion$/ ) && ( sub || mod || own || vip )
+ if explosions
+ if ( match[2]? ) && ( match[2].match( /^([0-9]+)/ ) && ( match[2].to_i > 1 ) && ( match[2].to_i <= 32 ) )
+ count = match[2].to_i
else
- pp ( match )
- bot.message( "##{settings["channel"]}", "| Invalid voice. To see list, use !voices" )
+ count = 1
+ end
+ explosions.values.sample( count ).each do | explosion |
+ obstemporarymediacreate( obs, "meta-foreground", explosion, "C:/cygwin64/home/user/effects/explosions/#{explosion}.webm" )
end
+ else
+ puts "explosions undefined"
end
- when 3
- else
- end
- elsif ( cmd =~ /^uptime$/ )
- obs5ipc.send( obsrequest( "GetStats", "GetStats" ))
- # !newimage [name] [url] (random|top|bottom|left|right|center)
- # download url, verify mimetype with libmagic, duplicate existing medialoop source, change "file" SourceSetting
- elsif ( ( cmd =~ /^sourcesettings$/ ) && ( mod || own || vip ) )
- if ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ )
-
- obs5ipc.send( obsrequest( "GetInputSettings", "GetInputSettings", JSON.parse( { "inputName" => match[2] }.to_json ) ))
- end
- elsif ( ( cmd =~ /^inputlist$/ ) && ( mod || own || vip ) )
- obs5ipc.send( obsrequest( "GetInputList", "GetInputList" ) )
- elsif ( ( cmd =~ /^scene$/ ) && ( mod || own || vip ) )
- if ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ )
- obs5ipc.send( obsrequest( "SetCurrentProgramScene", "SetCurrentProgramScene", JSON.parse( { "sceneName" => match[2] }.to_json ) ))
- else
- obs5ipc.send( obsrequest( "GetSceneList", "GetSceneList" ) )
- 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)$/ ) )
- obs5ipc.send( "\x10source #{match[2]}" )
- elsif ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ )
- obs5ipc.send( "\x10source #{match[2]}" )
- else
- obs5ipc.send( "\x10source" )
- end
- elsif ( ( cmd =~ /^filter$/ ) && ( mod || own ) )
- request = Hash( String, String | Bool ){
- "request-type" => "GetSourceFilters"
- }
- if ( match[2]? ) && ( filterargs = match[2].match( /^([a-zA-Z0-9-_]+) +([a-zA-Z0-9-_]+) (true|false)/ ) )
- request["request-type"] = "SetSourceFilterVisibility"
- request["message-id"] = "SetSourceFilterVisibility"
- request["sourceName"] = filterargs[1]
- request["filterName"] = filterargs[2]
- #request["filterEnabled"] = filterargs[3]
- filterargs[3] == "true" && ( request["filterEnabled"] = true )
- filterargs[3] == "false" && ( request["filterEnabled"] = false )
- obs4ipc.send( request.to_json )
- elsif ( match[2]? ) && ( filterargs = match[2].match( /^([a-zA-Z0-9-_]+) +([a-zA-Z0-9-_]+)/ ) )
- ircipc.send( { "##{settings["channel"]}", "Must provide provide a value of true or false as the third argument." } )
- elsif ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ )
- request["sourceName"] = match[2]
- request["message-id"] = "GetSourceFilters"
- obs4ipc.send( request.to_json )
- 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 == "create" && ( mod || own || vip ) )
- if ( match[2]? ) && ( match[2] =~ /^([a-zA-Z0-9-_]+)/ )
- obs4ipc.send( "\x10newsource-temporary #{match[2]}" )
- else
- ircipc.send( { "##{settings["channel"]}", "Must provide at least one source name as argument." } )
- end
- elsif ( cmd =~ /^(metaminute|youdied)$/ )
- obs4ipc.send( "\x10source-notransition media-#{cmd}" )
- elsif ( cmd =~ /^explosion$/ )
- if explosions
- explosion = explosions.sample( 1 )[0][1]
- puts explosion
- obs4ipc.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]+/
- twitchipc.send( { "get_user", match[2].to_u64 } )
- else
- twitchipc.send( { "get_user", settings["channel_id"].to_u64 } )
- end
- elsif ( ( cmd =~ /^(status|title)$/ ) && ( mod || own ) )
- begin
+ # FIXME: This is only half-implemented
+ elsif ( ( cmd =~ /^(user)$/ ) && ( mod || own ) )
+ if match[2]? && match[2] =~ /[a-z][a-z0-9_]+/
+ twitchipc.send( { "get_user", match[2] } )
+ elsif match[2]? && match[2] =~ /[0-9]+/
+ twitchipc.send( { "get_user", match[2].to_u64 } )
+ else
+ twitchipc.send( { "get_user", settings["channel_id"].to_u64 } )
+ end
+ elsif ( ( cmd =~ /^(status|title)$/ ) && ( mod || own ) )
if match[2]?
client.put_channel!( settings["channel_id"].to_u64, title: match[2] )
json = JSON.parse( client.get_channel( settings["channel_id"].to_u64 ) )
@@ -1188,11 +742,7 @@ loop do
json = JSON.parse( client.get_channel( settings["channel_id"].to_u64 ) )
ircipc.send( { "##{settings["channel"]}", "| Title is currently \"#{ json["data"][0]["title"] }\""} )
end
- rescue ex
- ircipc.send( { "##{settings["channel"]}", "| An error occurred! " + ex.message.to_s } )
- end
- elsif ( ( cmd =~ /^(game|category)$/ ) && ( mod || own ) )
- begin
+ elsif ( ( cmd =~ /^(game|category)$/ ) && ( mod || own ) )
if match[2]?
puts "2 matches"
client.put_channel!( settings["channel_id"].to_u64, game: match[2] )
@@ -1203,140 +753,138 @@ loop do
json = JSON.parse( client.get_channel( settings["channel_id"].to_u64 ) )
ircipc.send( { "##{settings["channel"]}", "| Game is currently \"#{ json["data"][0]["game_name"] }\""} )
end
- rescue ex
- ircipc.send( { "##{settings["channel"]}", "| An error occurred! " + ex.message.to_s } )
- end
- elsif ( ( cmd == "urban" ) && ( mod || own || sub || vip ) )
- if match[2]? && match[2] =~ /^([a-zA-Z0-9 -])+$/
- definition = urbandef( match[2] )
- ircipc.send( { "##{settings["channel"]}", definition[0,400] } )
- else
- ircipc.send( { "##{settings["channel"]}", "| Urban Dictionary search term should consist of letters, numbers, spaces, and/or hyphens." } )
- end
- elsif ( cmd == "matrix" )
- effectsmsg( settings, "overlay glmatrix" )
- elsif ( cmd =~ /juggle|juggler/ )
- effectsmsg( settings, "overlay juggler3d" )
- elsif ( cmd =~ /fireworks|firework/ )
- effectsmsg( settings, "overlay fireworkx" )
- elsif ( cmd == "pipes" )
- if match[2]? && match[2] =~ /^(fast|faster)$/
- effectsmsg( settings, "overlay pipes " + match[2] )
- else
- effectsmsg( settings, "overlay pipes" )
- end
- elsif ( cmd == "jellyfish" )
- effectsmsg( settings, "overlay hydrostat" )
- elsif ( cmd == "gluten" )
- effectsmsg( settings, "overlay flyingtoasters" )
- elsif ( cmd =~ /^(glsnake|glmatrix|gibson|xmatrix|flyingtoasters|moebiusgears|fireworkx|hydrostat|hypertorus|jigsaw|juggler3d|kaleidocycle|kumppa|molecule|noof|polyhedra)$/ )
- effectsmsg( settings, "overlay " + cmd )
- elsif ( cmd =~ /gltext|cowsay|xcowsay|cowfuscious/ )
- ( cmd == "cowfuscious" ) && ( cmd = "xcowsay" )
- if ( match[2]? ) && ( gltextargs = match[2].match( /^([0-9]+) +(.+)$/ ) )
- seconds=UInt64.new( 1 )
- seconds=gltextargs[1].to_u64
- if ( own || mod || vip )
+ elsif ( ( cmd == "urban" ) && ( mod || own || sub || vip ) )
+ if match[2]? && match[2] =~ /^([a-zA-Z0-9 -])+$/
+ definition = urbandef( match[2] )
+ ircipc.send( { "##{settings["channel"]}", definition[0,400] } )
+ # FIXME: maybe somehow fold this into the existing voice logic
+ t2s( t2sipc, settings, userdir, chatuser, definition[0,400] )
+ else
+ ircipc.send( { "##{settings["channel"]}", "| Urban Dictionary search term should consist of letters, numbers, spaces, and/or hyphens." } )
+ end
+ elsif ( cmd == "matrix" )
+ effectsmsg( settings, "overlay glmatrix" )
+ elsif ( cmd =~ /juggle|juggler/ )
+ effectsmsg( settings, "overlay juggler3d" )
+ elsif ( cmd =~ /fireworks|firework/ )
+ effectsmsg( settings, "overlay fireworkx" )
+ elsif ( cmd == "pipes" )
+ if match[2]? && match[2] =~ /^(fast|faster)$/
+ effectsmsg( settings, "overlay pipes " + match[2] )
+ else
+ effectsmsg( settings, "overlay pipes" )
+ end
+ elsif ( cmd == "jellyfish" )
+ effectsmsg( settings, "overlay hydrostat" )
+ elsif ( cmd == "gluten" )
+ effectsmsg( settings, "overlay flyingtoasters" )
+ elsif ( cmd =~ /^(glsnake|glmatrix|gibson|xmatrix|flyingtoasters|moebiusgears|fireworkx|hydrostat|hypertorus|jigsaw|juggler3d|kaleidocycle|kumppa|molecule|noof|polyhedra)$/ )
+ effectsmsg( settings, "overlay " + cmd )
+ elsif ( cmd =~ /gltext|cowsay|xcowsay|cowfuscious/ )
+ ( cmd == "cowfuscious" ) && ( cmd = "xcowsay" )
+ if ( match[2]? ) && ( gltextargs = match[2].match( /^([0-9]+) +(.+)$/ ) )
+ seconds=UInt64.new( 1 )
+ seconds=gltextargs[1].to_u64
+ if ( own || mod || vip )
+ effectsmsg( settings, "overlay #{cmd} #{match[2]}" )
+ puts "matched #{cmd} #{match[2]}"
+ elsif ( sub )
+ if ( seconds > 20 )
+ seconds=20
+ end
+ effectsmsg( settings, "overlay #{cmd} #{seconds} #{gltextargs[2]}" )
+ puts "matched #{cmd} #{seconds} #{gltextargs[2]}"
+ else
+ if ( seconds > 5 )
+ seconds=5
+ end
+ effectsmsg( settings, "overlay #{cmd} #{seconds} #{gltextargs[2]}" )
+ puts "matched #{cmd} #{seconds} #{gltextargs[2]}"
+ end
+ elsif match[2]? && match[2] =~ /^.+$/
effectsmsg( settings, "overlay #{cmd} #{match[2]}" )
puts "matched #{cmd} #{match[2]}"
- elsif ( sub )
- if ( seconds > 20 )
- seconds=20
- end
- effectsmsg( settings, "overlay #{cmd} #{seconds} #{gltextargs[2]}" )
- puts "matched #{cmd} #{seconds} #{gltextargs[2]}"
else
- if ( seconds > 5 )
- seconds=5
+ effectsmsg( settings, "overlay #{cmd}" )
+ puts "failed to match gltext"
+ end
+ elsif ( cmd =~ /^(cow(s|)|cowabunga|holycow$)/ )
+ if match[2]? && match[2] =~ /^([0-9])+$/
+ effectsmsg( settings, "overlay bouncingcow #{match[2]}" )
+ else
+ effectsmsg( settings, "overlay bouncingcow" )
+ end
+ elsif ( cmd == "overlay" )
+ if match[2]? && match[2] =~ /^[a-z]+$/
+ 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" )
+ ircipc.send( { "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/img/hackerman.jpg" } )
+ elsif ( cmd =~ /^(songrequest|sr)$/ ) && match[2]?
+ puts ("song detected: #{match[2]}")
+ if ( ( match[2] =~ /list=/ ) && ( match[2] !~ /v=/ ) )
+ ircipc.send( { "##{settings["channel"]}", "| Lists are not accepted.\n" } )
+ elsif Process.run( "sraddsong.sh", {match[2]} )
+ m = MPD::Client.new
+ currentsong = m.currentsong
+ if ( currentsong ) && ( currentsong["file"].to_s == "http://music/music.ogg" )
+ m.next
end
- effectsmsg( settings, "overlay #{cmd} #{seconds} #{gltextargs[2]}" )
- puts "matched #{cmd} #{seconds} #{gltextargs[2]}"
+ if ( status = m.status ) && ( playlistinfo = m.playlistinfo )
+ ircipc.send( { "##{settings["channel"]}", "| " + playlistinfo[ status["playlistlength"].to_i - 1 ]["file"].to_s + " added in position " + status["playlistlength"].to_s } )
+ else
+ ircipc.send( { "##{settings["channel"]}", "| A failure occured." } )
+ end
+ m.disconnect
+ else
end
- elsif match[2]? && match[2] =~ /^.+$/
- effectsmsg( settings, "overlay #{cmd} #{match[2]}" )
- puts "matched #{cmd} #{match[2]}"
- else
- effectsmsg( settings, "overlay #{cmd}" )
- puts "failed to match gltext"
- end
- elsif ( cmd =~ /^(cow(s|)|cowabunga|holycow$)/ )
- if match[2]? && match[2] =~ /^([0-9])+$/
- effectsmsg( settings, "overlay bouncingcow #{match[2]}" )
- else
- effectsmsg( settings, "overlay bouncingcow" )
- end
- elsif ( cmd == "overlay" )
- if match[2]? && match[2] =~ /^[a-z]+$/
- 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" )
- ircipc.send( { "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/img/hackerman.jpg" } )
- elsif ( cmd =~ /^(songrequest|sr)$/ ) && match[2]?
- puts ("song detected: #{match[2]}")
- if ( ( match[2] =~ /list=/ ) && ( match[2] !~ /v=/ ) )
- ircipc.send( { "##{settings["channel"]}", "| Lists are not accepted.\n" } )
- elsif Process.run( "sraddsong.sh", {match[2]} )
+ elsif ( cmd =~ /^current(|song)$/ )
m = MPD::Client.new
- currentsong = m.currentsong
- if ( currentsong ) && ( currentsong["file"].to_s == "http://music/music.ogg" )
- m.next
- end
- if ( status = m.status ) && ( playlistinfo = m.playlistinfo )
- ircipc.send( { "##{settings["channel"]}", "| " + playlistinfo[ status["playlistlength"].to_i - 1 ]["file"].to_s + " added in position " + status["playlistlength"].to_s } )
+ if ( currentsong = m.currentsong )
+ ircipc.send( { "##{settings["channel"]}", "| Currently playing: " + currentsong["file"].to_s } )
else
ircipc.send( { "##{settings["channel"]}", "| A failure occured." } )
end
m.disconnect
- else
- end
- elsif ( cmd =~ /^current(|song)$/ )
- m = MPD::Client.new
- if ( currentsong = m.currentsong )
- ircipc.send( { "##{settings["channel"]}", "| Currently playing: " + currentsong["file"].to_s } )
- else
- ircipc.send( { "##{settings["channel"]}", "| A failure occured." } )
- end
- m.disconnect
- elsif ( cmd =~ /^seek$/ )
- if ( ( match[2] ) && ( match[2] =~ /([+-]|)[0-9]/ ) )
- m = MPD::Client.new
- m.seekcur( match[2] )
- if ( ( musicstatus = m.status ) && ( pos = musicstatus["elapsed"].to_f.to_u64 ) )
- min=( pos / 60 ).to_u64
- sec=( pos % 60 ).to_u64
- ircipc.send( { "##{settings["channel"]}", "| " + sprintf( "2%d:2%d", min, sec ) } )
+ elsif ( cmd =~ /^seek$/ )
+ if ( ( match[2] ) && ( match[2] =~ /([+-]|)[0-9]/ ) )
+ m = MPD::Client.new
+ m.seekcur( match[2] )
+ if ( ( musicstatus = m.status ) && ( pos = musicstatus["elapsed"].to_f.to_u64 ) )
+ min=( pos / 60 ).to_u64
+ sec=( pos % 60 ).to_u64
+ ircipc.send( { "##{settings["channel"]}", "| " + sprintf( "2%d:2%d", min, sec ) } )
+ else
+ ircipc.send( { "##{settings["channel"]}", "| An error occurred. " } )
+ end
+ m.disconnect
else
- ircipc.send( { "##{settings["channel"]}", "| An error occurred. " } )
+ ircipc.send( { "##{settings["channel"]}", "| Seek requires an argument of an absolute position in integer number of seconds, or a relative position in in signed integer number of seconds such as +60" } )
end
- m.disconnect
- else
- ircipc.send( { "##{settings["channel"]}", "| Seek requires an argument of an absolute position in integer number of seconds, or a relative position in in signed integer number of seconds such as +60" } )
- end
- elsif ( cmd =~ /^next(|song)$/ )
- m = MPD::Client.new
- m.next
- if ( status = m.status ) && ( status["playlistlength"].to_i > 0 )
- if ( currentsong = m.nextsong )
- ircipc.send( { "##{settings["channel"]}", "| Currently playing: " + currentsong["file"].to_s } )
+ elsif ( cmd =~ /^next(|song)$/ )
+ m = MPD::Client.new
+ m.next
+ if ( status = m.status ) && ( status["playlistlength"].to_i > 0 )
+ if ( currentsong = m.nextsong )
+ ircipc.send( { "##{settings["channel"]}", "| Currently playing: " + currentsong["file"].to_s } )
+ else
+ ircipc.send( { "##{settings["channel"]}", "| A failure occured." } )
+ end
else
- ircipc.send( { "##{settings["channel"]}", "| A failure occured." } )
+ ircipc.send( { "##{settings["channel"]}", "| Playlist is now empty." } )
end
- else
- ircipc.send( { "##{settings["channel"]}", "| Playlist is now empty." } )
- end
- m.disconnect
- elsif ( cmd == "followage" )
- begin
+ m.disconnect
+ elsif ( cmd == "followage" )
if match[2]?
args = match[2].split(/\s/)
if args[1]?
@@ -1356,10 +904,10 @@ loop do
puts json
ircipc.send( { "##{settings["channel"]}", "| " + json["data"][0]["followed_at"].to_s } )
end
- rescue ex
- ircipc.send( { "##{settings["channel"]}", "An error occurred! " + ex.message.to_s } )
end
- end
+ rescue ex
+ puts ex
+ ircipc.send( { "##{settings["channel"]}", "An error occurred! " + ex.message.to_s } )
end
end