diff options
-rw-r--r-- | crystal/irc.cr | 490 |
1 files changed, 354 insertions, 136 deletions
diff --git a/crystal/irc.cr b/crystal/irc.cr index 5d2283c..9ac7e8b 100644 --- a/crystal/irc.cr +++ b/crystal/irc.cr @@ -15,15 +15,37 @@ end settings = Hash(String, String).new +settings["configdir"] = Path.home./("/.config/bungmobott/").to_s +settings["statedir"] = Path.home./("/.local/state/bungmobott/").to_s + +if ENV["LOCALAPPDATA"]? + settings["configdir"] = Path.windows( ENV["LOCALAPPDATA"] + "\\bungmobott\\" ).to_s + settings["statedir"] = Path.windows( ENV["LOCALAPPDATA"] + "\\bungmobott\\state\\" ).to_s +end + +ENV["XDG_CONFIG_HOME"]? && ( settings["configdir"] = ENV["XDG_CONFIG_HOME"] + "/bungmobott/" ) +#ENV["XDG_DATA_HOME"]? && ( settings["datadir"] = ENV["XDG_DATA_HOME"] ) # unused? +ENV["XDG_STATE_HOME"]? && ( settings["statedir"] = ENV["XDG_STATE_HOME"] + "/bungmobott/" ) + +Dir.mkdir_p( settings["configdir"] ) +Dir.mkdir_p( settings["statedir"] ) + settings["home"] = Path.home.to_s ["access_token", "channel", "channel_id", "client_id", "client_id_twitch"].each do |key| begin - settings[key] = File.read( settings["home"] + "/.config/twitch/" + key ).chomp + settings[key] = File.read( settings["configdir"] + key ).chomp rescue IO::Error - STDERR.puts "Warning: Missing " + settings["home"] + "/.config/twitch/" + key + STDERR.puts "Warning: Missing " + settings["configdir"] + key end end +if File.exists?( settings["configdir"] + "chatuser" ) + File.read( settings["configdir"] + "chat_user" ).chomp +else + STDERR.puts "Warning: Missing " + settings["configdir"] + "chat_user; using configured channel instead." + settings["chat_user"] = settings["channel"] +end + obsipc = Channel( String ).new ircipc = Channel( Tuple( String, String ) ).new @@ -40,65 +62,65 @@ def urbandef( term : String ) return json["list"][0]["definition"].to_s.gsub( /[\[\]]/, "" ).gsub( /\n/, "" ) end -def ircmsg( home : String, channel : String, msg : String) - if msg !~ /(\r|\n)/ - sock = Socket.unix +#def ircmsg( home : String, channel : String, msg : String) +# if msg !~ /(\r|\n)/ +# sock = Socket.unix # sock.connect Socket::UNIXAddress.new( home + "/.irssi/twitch-socket".to_s, type: Socket::Type::DGRAM ) - sock.connect Socket::UNIXAddress.new( home + "/.irssi/twitch-socket".to_s ) - sock.puts( "#" + channel + " " + msg ) +# sock.connect Socket::UNIXAddress.new( home + "/.irssi/twitch-socket".to_s ) +# sock.puts( "#" + channel + " " + msg ) +# sock.close +# end +#end + +def t2smsg( settings : Hash(String, String), msg : String) + if File.exists?( settings["statedir"] + "/.t2s.sock" ) + sock = Socket.unix + sock.connect Socket::UNIXAddress.new( settings["statedir"] + "/.t2s.sock" ) + sock.puts( msg ) sock.close end -end - -def t2smsg( msg : String) - sock = Socket.unix - sock.connect Socket::UNIXAddress.new( ENV["HOME"] + "/.t2s.sock" ) - sock.puts( msg ) - sock.close rescue ex puts ex end -def effectsmsg( msg : String ) - sock = Socket.unix - sock.connect Socket::UNIXAddress.new( ENV["HOME"] + "/.effects.sock" ) - sock.puts( msg ) - sock.close +def effectsmsg( settings : Hash(String, String), msg : String ) + if File.exists?( settings["statedir"] + "/.effects.sock" ) + sock = Socket.unix + sock.connect Socket::UNIXAddress.new( settings["statedir"] + "/.effects.sock" ) + sock.puts( msg ) + sock.close + end rescue ex puts ex end -def userlog( settings : Hash(String, String), message : FastIRC::Message ) +def userlog( ircipc : Channel, settings : Hash(String, String), message : FastIRC::Message ) unless ( ( prefix = message.prefix ) && ( chatuser = prefix.source ) && ( uid = message.tags["user-id"]? ) ) return nil end - basedir = settings["home"] + "/.cache/twitchtools" + basedir = settings["statedir"] userdir = basedir + "/uids/" + uid - unless File.directory?( userdir ) - Dir.mkdir( userdir ) - end - unless File.directory?( userdir + "/names" ) - Dir.mkdir( userdir + "/names" ) - end - unless File.symlink?( userdir + "/names/" + chatuser ) - namelatest = "" - datelatest = Time::UNIX_EPOCH - Dir.each_child( userdir + "/names/" ) do |name| - namedate = File.info( userdir + "/names/" + name ).modification_time - if namedate > datelatest - namelatest = name - datelatest = namedate - end + Dir.mkdir_p( basedir + "/names" ) + Dir.mkdir_p( userdir + "/names" ) + unless testrefuser2uid( userdir + "/names/" + chatuser ) + namelatest = "" + datelatest = Time::UNIX_EPOCH + Dir.each_child( userdir + "/names/" ) do |name| + namedate = File.info( userdir + "/names/" + name ).modification_time + if namedate > datelatest + namelatest = name + datelatest = namedate end - unless namelatest.empty? - ircmsg( settings["home"], settings["channel"], "Rename detected: #{uid}: #{namelatest} -> #{chatuser}" ) - end - File.symlink( "../", userdir + "/names/" + chatuser ) end - unless File.symlink?( basedir + "/names/" + chatuser ) - File.symlink( "../uids/" + uid, basedir + "/names/" + chatuser ) + unless namelatest.empty? + ircipc.send( { "##{settings["channel"]}", "Rename detected: #{uid}: #{namelatest} -> #{chatuser}" } ) end - unless ( message.params[0] == "#bungmonkey" ) + genrefuser2uid( userdir + "/names/" + chatuser, uid, 3 ) + end + unless testrefuser2uid( basedir + "/names/" + chatuser ) + genrefuser2uid( basedir + "/names/" + chatuser, uid, 1 ) + end + unless ( message.params[0] == "##{settings["channel"]}" ) return nil end return ( [ chatuser, uid, userdir ] ) @@ -111,7 +133,7 @@ def getvoice( settings : Hash(String, String), userdir : String, chatuser : Stri voice_output = File.read( userdir + "/voice" ).chomp voice_setting = voice_output else - voice_output = File.read( settings["home"] + "/voicelist.txt" ).chomp.split( "\n" ).sample( 1 )[0].chomp + voice_output = File.read( settings["configdir"] + "/voicelist.txt" ).chomp.split( "\n" ).sample( 1 )[0].chomp voice_setting = "random" end if File.exists?( userdir + "/voicesub" ) @@ -126,17 +148,134 @@ end def t2s( settings : Hash(String, String), userdir : String, chatuser : String, text : String ) if ( text !~ /^ *(!|\|)/ ) namesub, voice_setting, voice = getvoice( settings, userdir, chatuser ) - t2stext = text.gsub( /http(s|):\/\/([a-z0-9.]+)\/[a-zA-Z0-9\/&=%-_]+/, "link to ${2}" ) - t2stext = t2stext.gsub( /rrr.+/, "rr" ) - t2smsg( "#{voice} #{namesub} #{t2stext}" ) + subs = Array( Tuple( Regex, String ) ){ + { /http(s|):\/\/([a-z0-9.-]+)\/[a-zA-Z0-9\/&=%-_]+/, "link to \\2" }, + { /([^a-zA-Z0-9])-/, "\\1 dash "}, + { /\|/, " vertical bar "}, + { /\`/, " grave accent "}, + { /\+/, " plus "}, + { /×/, " multiplied by "}, + { /=/, " equals "}, + { /\//, " slash "}, + { /\\/, " backslash "}, + { /@/, " at "}, + { /&/, " and "}, + { />/, " greater than "}, + { /</, " less than "}, + { /_/, " underscore "}, + { /\.\.\./, " dot dot dot "}, + { /\^/, " circumflex accent "}, + { /\#/, " octothorpe "}, + { /:([^ ])/, " colon \\1"}, + { /;([^ ])/, " semicolon \\1"}, + { /\.([^ ])/, " dot \\1"}, + { /\%([^ ])/, " percent sign \\1"}, + { /!([^ ])/, " tchik \\1"}, + { /([^ ])\$/, "\\1 dollar sign "}, + { /0/, " zero " }, + { /1/, " one " }, + { /2/, " two " }, + { /3/, " three " }, + { /4/, " four " }, + { /5/, " five " }, + { /6/, " six " }, + { /7/, " seven " }, + { /8/, " eight " }, + { /9/, " nine " }, + { /rrr.+/, "rr" }, + }.each do | subtuple | + text = text.gsub( subtuple[0], subtuple[1] ) + end + {% if flag?(:windows) %} + if ( match = voice.match( /^Microsoft-([A-Za-z]+)/ ) ) + if ( match[1] =~ /Lili|Mary|Mike|Sam|Anna/ ) + msttsvoice="Microsoft #{match[1]}" + else + msttsvoice="Microsoft #{match[1]} Desktop" + end + pp msttsvoice + p = Process.new( "powershell.exe", [ "-Command", " + echo test; + Add-Type -AssemblyName System.Speech; + $speak = New-Object System.Speech.Synthesis.SpeechSynthesizer; + $speak.SelectVoice(\"#{msttsvoice}\"); + $speak.Speak($Input); + $speak.Finalize($Input); + "], + input: Process::Redirect::Pipe, output: STDOUT ) + p.input.puts "#{namesub} #{text}" + p.input.close + end + {% elsif flag?(:unix) %} + t2smsg( settings, "#{voice} #{namesub} #{text}" ) + {% end %} return( voice ) else return( nil ) 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["type"] == "scene" + scenes[source.to_s].each do | metasource, metasourcedata | + sourcemap[metasource.to_s] = source.to_s + end + end + end + return sourcemap +end + +macro testrefuser2uid( path ) + {% if flag?(:windows) %} + File.exists?( {{path}} ) && ( File.read( {{ path }} ) =~ /^[0-9]+$/ ) + {% elsif flag?(:unix) %} + File.symlink?( {{path}} ) + {% end %} +end + +macro genrefuser2uid( path, uid, depth ) + {% if flag?(:windows) %} + File.write( {{path}}, {{uid}}.to_s ) + {% elsif flag?(:unix) %} + pp "unix" + File.symlink( "../"*{{depth}} + "uids/#{{{uid}}}", {{path}} ) + {% 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 -File.each_line( settings["home"] + "/voicelist.txt" ) do |line| +File.each_line( settings["configdir"] + "/voicelist.txt" ) do |line| voices[ line.downcase ] = line end @@ -149,10 +288,36 @@ spawn do obs_pubsub = HTTP::WebSocket.new( URI.parse( "ws://127.0.0.1:4444/" ), HTTP::Headers{"Cookie" => "SESSIONID=1234"} ) # Outgoing + # state + currentscene = "unknown" + # scenename => { sourcename => { property => value } + scenes = Hash( String, Hash( String, Hash( String, Int64 | Float64 | Bool | String ) ) ).new spawn do while msg = obsipc.receive pp msg - obs_pubsub.send( msg ) + if ( match = msg.match( /^\x10source-toggle ([a-z0-9-_]+)/ ) ) + source = match[1] + sources = reversesource( scenes, currentscene ) + pp sources + request = Hash( String, String | Bool ).new + request["request-type"] = "SetSceneItemProperties" + request["message-id"] = "SetSceneItemProperties" + #request["request-type"] = "GetSceneItemProperties" + request["message-id"] = "toggle-source" + scene = sources[source] + request["scene-name"] = sources[source] + request["item"] = source + if scenes[scene][source]["render"] == true + request["visible"] = false + else + request["visible"] = true + end + obs_pubsub.send( request.to_json ) + elsif ( match = msg.match( /^\x10source ([a-z0-9-_]+)/ ) ) + # FIXME + else + obs_pubsub.send( msg ) + end end end # Incoming @@ -168,6 +333,8 @@ spawn do next # so skip them when "SwitchScenes" ircipc.send( { "#" + settings["channel"], "| obs: switched scene to " + ( json["scene-name"]?.as_s? || "unknown" ) } ) + currentscene = json["scene-name"].as_s + puts "currentscene is " + currentscene when "MediaEnded" request = Hash( String, String | Bool ){ "request-type" => "SetSceneItemProperties", @@ -175,8 +342,19 @@ spawn do "item" => json["sourceName"].as_s, "visible" => false } - #ircipc.send( { "##{settings["channel"]}", "Disabling #{json["sourceName"].as_s}" } ) + ircipc.send( { "##{settings["channel"]}", "Disabling #{json["sourceName"].as_s}" } ) obsipc.send( request.to_json ) + #"sourceName": "media-youdied" + puts "currentscene is " + currentscene + if ( ( json["sourceName"].as_s =~ /^media-/ ) && ( currentscene != "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 #{json["sourceName"].as_s}" } ) + obsipc.send( request.to_json ) + end when "SourceFilterVisibilityChanged" ircipc.send( { "##{settings["channel"]}", "| obs: source #{json["sourceName"]} filter #{json["filterName"]} visibility is now #{json["filterEnabled"]}" } ) when "SceneItemVisibilityChanged" @@ -187,11 +365,27 @@ spawn do print( "\n") case json["message-id"]? when "GetCurrentScene" + currentscene = json["name"].as_s ircipc.send( { "#" + settings["channel"], "| obs: " + json["sources"].as_a.map{ |source| source["name"] }.join(", ") } ) when "GetSourceFilters" ircipc.send( { "#" + settings["channel"], "| obs: " + json["filters"].as_a.map{ |filter| filter["name"] }.join(", ") } ) when "GetSceneList" ircipc.send( { "#" + settings["channel"], "| obs: " + json["scenes"].as_a.map{ |scene| scene["name"] }.join(", ") } ) + 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["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 ) + } + } + pp scenes when "toggle-filter" name = json["filters"][0]["name"].as_s visible = ( json["filters"][0]["enabled"].as_bool == false ) @@ -205,17 +399,24 @@ spawn do obsipc.send( "{ \"request-type\": \"SetSceneItemProperties\", \"message-id\": \"SetSceneItemProperties\", \"item\": \"#{name}\", \"visible\": #{visible} }" ) end end + request = Hash( String, String ){ + "request-type" => "GetCurrentScene", + "message-id" => "GetCurrentScene", + } + obsipc.send( request.to_json ) + obs_pubsub.run - 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 : Socket::ConnectError # these are a bit noisy pp ex obs_pubsub && obs_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 - ircmsg( settings["home"], settings["channel"], "irc.cr:obs: " + ex.to_s.gsub(/\r|\n/, ' ') ) - puts ex + pp ex + pp ex.backtrace? + ircipc.send( { "#" + settings["channel"], "irc.cr:obs: " + ex.to_s.gsub(/\r|\n/, ' ') } ) obs_pubsub && obs_pubsub.close end sleep 10 @@ -227,24 +428,27 @@ end loop do begin - bot = Twitch::IRC::Client.new( nick: settings["channel"], token: "oauth:" + settings["access_token"], log_mode: true ) + bot = Twitch::IRC::Client.new( nick: settings["chat_user"], token: "oauth:" + settings["access_token"], log_mode: true ) bot.tags = [ "membership", "tags", "commands" ] # Outgoing + # "Most IRC servers limit messages to 512 bytes in length, including the trailing CR-LF characters." + # PRIVMSG #channel message\r\n spawn do - while msgtuple = ircipc.receive -bot.message( msgtuple[0], msgtuple[1][0..480] ) + while msgtuple = ircipc.receive # why does this need to be a tuple? + sizelimit=( 512 - ( msgtuple[0].size + 11 ) ) + bot.message( msgtuple[0], msgtuple[1][0..sizelimit] ) # limit size end end # Create a handler to process incoming messages bot.on_message do |message| spawn do - next unless ( userlogreturn = userlog( settings, message ) ) + next unless ( userlogreturn = userlog( ircipc, settings, message ) ) chatuser, uid, userdir = userlogreturn if ( chatuser == "pipne" ) && ( ( Time.utc.to_unix - snifflast ) >= 14400 ) snifflast = Time.utc.to_unix - t2smsg( chatuser + " sniff null" ) + t2smsg( settings, chatuser + " sniff null" ) end if ( t2sreturn = t2s( settings, userdir, chatuser, message.params[1] ) ) lastvoice.insert( 0, t2sreturn ) @@ -260,9 +464,9 @@ bot.message( msgtuple[0], msgtuple[1][0..480] ) case message.params[1].split( " " ).size when 1 if File.exists?( userdir + "/voicesub" ) - bot.message( "#bungmonkey", "| Current name substitution is \"#{File.read( userdir + "/voicesub" )}\"." ) + bot.message( "##{settings["channel"]}", "| Current name substitution is \"#{File.read( userdir + "/voicesub" )}\"." ) else - bot.message( "#bungmonkey", "| Current name substitution is disabled." ) + bot.message( "##{settings["channel"]}", "| Current name substitution is disabled." ) end else if match[2]? @@ -270,54 +474,54 @@ bot.message( msgtuple[0], msgtuple[1][0..480] ) if voicesub =~ /^(disabled|null|disable|none)$/ if File.exists?( userdir + "/voicesub" ) File.delete( userdir + "/voicesub" ) - bot.message( "#bungmonkey", "| Name substitution for #{chatuser} is now disabled." ) + bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is now disabled." ) else - bot.message( "#bungmonkey", "| Name substitution for #{chatuser} is already disabled." ) + bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is already disabled." ) end else - File.directory?( userdir ) || Dir.mkdir( userdir ) + Dir.mkdir_p( userdir ) File.write( userdir + "/voicesub", voicesub ) - bot.message( "#bungmonkey", "| Name substitution for #{chatuser} is now \"#{File.read( userdir + "/voicesub" )}\"." ) + bot.message( "##{settings["channel"]}", "| Name substitution for #{chatuser} is now \"#{File.read( userdir + "/voicesub" )}\"." ) end end end elsif ( cmd =~ /^(commands|help)$/ ) - bot.message( "#bungmonkey", "| https://bungmonkey.omgwallhack.org/txt/commands.txt" ) + bot.message( "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/txt/commands.txt" ) elsif ( cmd =~ /^(dexem)$/ ) - ircipc.send( { "#bungmonkey", "| You're doing great work, Dexem!" } ) + ircipc.send( { "##{settings["channel"]}", "| You're doing great work, Dexem!" } ) elsif ( ( cmd == "lastvoice" ) && ( mod || sub ) ) unless lastvoice.empty? - bot.message( "#bungmonkey", "| Last voices were " + lastvoice.join( ", " ) ) + bot.message( "##{settings["channel"]}", "| Last voices were " + lastvoice.join( ", " ) ) else - bot.message( "#bungmonkey", "| No voices used so far." ) + bot.message( "##{settings["channel"]}", "| No voices used so far." ) end elsif ( ( cmd =~ /^(voices|voicelist)$/ ) && ( mod || sub ) ) - bot.message( "#bungmonkey", "| https://bungmonkey.omgwallhack.org/voicelist.txt" ) + 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( "#bungmonkey", "| Current voice is #{voice_setting}" ) + 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( "#bungmonkey", "| Voice for #{chatuser} is now random." ) + bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is now random." ) else - bot.message( "#bungmonkey", "| Voice for #{chatuser} is already random." ) + bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is already random." ) end elsif voices.has_key?( voice ) csvoice = voices[voice] - File.directory?( userdir ) || Dir.mkdir( userdir ) + Dir.mkdir_p( userdir ) File.write( userdir + "/voice", csvoice ) pp userdir - bot.message( "#bungmonkey", "| Voice for #{chatuser} is now #{File.read( userdir + "/voice" )}." ) + bot.message( "##{settings["channel"]}", "| Voice for #{chatuser} is now #{File.read( userdir + "/voice" )}." ) # TODO: make separate script to print streamelements URLs to client machine else pp ( match ) - bot.message( "#bungmonkey", "| Invalid voice. To see list, use !voices" ) + bot.message( "##{settings["channel"]}", "| Invalid voice. To see list, use !voices" ) end end when 3 @@ -332,23 +536,28 @@ bot.message( msgtuple[0], msgtuple[1][0..480] ) elsif ( ( cmd =~ /^source$/ ) && ( mod || own ) ) request = Hash( String, String | Bool ).new if ( match[2]? ) && ( sourceargs = match[2].match( /^([a-zA-Z0-9-_]+) +(true|false)/ ) ) - request["request-type"] = "SetSceneItemProperties" - request["message-id"] = "SetSceneItemProperties" - request["item"] = sourceargs[1] - sourceargs[2] == "true" && ( request["visible"] = true ) - sourceargs[2] == "false" && ( request["visible"] = false ) - - ircipc.send( { "##{settings["channel"]}", "Setting source #{sourceargs[1]} visibility to #{sourceargs[2]}" } ) - obsipc.send( request.to_json ) +# request["request-type"] = "SetSceneItemProperties" +# request["message-id"] = "SetSceneItemProperties" +# request["item"] = sourceargs[1] +# sourceargs[2] == "true" && ( request["visible"] = true ) +# sourceargs[2] == "false" && ( request["visible"] = false ) +# +# ircipc.send( { "##{settings["channel"]}", "Setting source #{sourceargs[1]} visibility to #{sourceargs[2]}" } ) + obsipc.send( "\x10source #{match[2]}" ) elsif ( match[2]? && match[2] =~ /^[a-zA-Z0-9-_]+$/ ) - request["request-type"] = "GetSceneItemProperties" - request["message-id"] = "toggle-source" - request["item"] = match[2] - obsipc.send( request.to_json ) + #sources = reversesource( scenes, currentscene ) + #request["request-type"] = "SetSceneItemProperties" + #request["message-id"] = "SetSceneItemProperties" + #request["request-type"] = "GetSceneItemProperties" + #request["message-id"] = "toggle-source" + #request["item"] = match[2] + #obsipc.send( request.to_json ) + obsipc.send( "\x10source-toggle #{match[2]}" ) else - request["request-type"] = "GetCurrentScene" - request["message-id"] = "GetCurrentScene" - obsipc.send( request.to_json ) +# request["request-type"] = "GetCurrentScene" +# request["message-id"] = "GetCurrentScene" +# obsipc.send( request.to_json ) + obsipc.send( "\x10source #{match[2]}" ) end elsif ( ( cmd =~ /^filter$/ ) && ( mod || own ) ) request = Hash( String, String | Bool ){ @@ -375,6 +584,12 @@ bot.message( msgtuple[0], msgtuple[1][0..480] ) else ircipc.send( { "##{settings["channel"]}", "Must provide at least one source name as argument, and optionally one filter name." } ) end + elsif ( cmd == "duplicate" && ( mod || own || vip ) ) + if ( match[2]? ) && ( filterargs = match[2].match( /^([a-zA-Z0-9-_]+)/ ) ) + temporaryduplicate( match[2] ) + else + ircipc.send( { "##{settings["channel"]}", "Must provide at least one source name as argument." } ) + end elsif ( cmd =~ /^(metaminute|youdied)$/ ) request = Hash( String, String | Bool ).new request["request-type"] = "SetSceneItemProperties" @@ -393,13 +608,13 @@ bot.message( msgtuple[0], msgtuple[1][0..480] ) 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 ) ) - ircmsg( settings["home"], settings["channel"], "Title is now \"#{ json["data"][0]["title"] }\"") + ircipc.send( { "##{settings["channel"]}", "Title is now \"#{ json["data"][0]["title"] }\""} ) else json = JSON.parse( client.get_channel( settings["channel_id"].to_u64 ) ) - ircmsg( settings["home"], settings["channel"], "| Title is currently \"#{ json["data"][0]["title"] }\"") + ircipc.send( { "##{settings["channel"]}", "| Title is currently \"#{ json["data"][0]["title"] }\""} ) end rescue ex - ircmsg( settings["home"], settings["channel"], "| An error occurred! " + ex.message.to_s ) + ircipc.send( { "##{settings["channel"]}", "| An error occurred! " + ex.message.to_s } ) end elsif ( ( cmd =~ /^(game|category)$/ ) && ( mod || own ) ) begin @@ -407,85 +622,88 @@ bot.message( msgtuple[0], msgtuple[1][0..480] ) puts "2 matches" client.put_channel!( settings["channel_id"].to_u64, game: match[2] ) json = JSON.parse( client.get_channel( settings["channel_id"].to_u64 ) ) - ircmsg( settings["home"], settings["channel"], "Game is now \"#{ json["data"][0]["game_name"] }\"") + ircipc.send( { "##{settings["channel"]}", "Game is now \"#{ json["data"][0]["game_name"] }\""} ) else puts "1 matches" json = JSON.parse( client.get_channel( settings["channel_id"].to_u64 ) ) - ircmsg( settings["home"], settings["channel"], "| Game is currently \"#{ json["data"][0]["game_name"] }\"") + ircipc.send( { "##{settings["channel"]}", "| Game is currently \"#{ json["data"][0]["game_name"] }\""} ) end rescue ex - ircmsg( settings["home"], settings["channel"], "| An error occurred! " + ex.message.to_s ) + 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] ) - ircmsg( settings["home"], settings["channel"], definition[0,400] ) + ircipc.send( { "##{settings["channel"]}", definition[0,400] } ) else - ircmsg( settings["home"], settings["channel"], "| Urban Dictionary search term should consist of letters, numbers, spaces, and/or hyphens." ) + ircipc.send( { "##{settings["channel"]}", "| Urban Dictionary search term should consist of letters, numbers, spaces, and/or hyphens." } ) end elsif ( cmd == "matrix" ) - effectsmsg( "overlay glmatrix" ) + effectsmsg( settings, "overlay glmatrix" ) elsif ( cmd =~ /juggle|juggler/ ) - effectsmsg( "overlay juggler3d" ) + effectsmsg( settings, "overlay juggler3d" ) elsif ( cmd =~ /fireworks|firework/ ) - effectsmsg( "overlay fireworkx" ) + effectsmsg( settings, "overlay fireworkx" ) elsif ( cmd == "pipes" ) if match[2]? && match[2] =~ /^(fast|faster)$/ - effectsmsg( "overlay pipes " + match[2] ) + effectsmsg( settings, "overlay pipes " + match[2] ) else - effectsmsg( "overlay pipes" ) + effectsmsg( settings, "overlay pipes" ) end elsif ( cmd == "jellyfish" ) - effectsmsg( "overlay hydrostat" ) + effectsmsg( settings, "overlay hydrostat" ) elsif ( cmd == "gluten" ) - effectsmsg( "overlay flyingtoasters" ) + effectsmsg( settings, "overlay flyingtoasters" ) elsif ( cmd =~ /^(glsnake|glmatrix|gibson|xmatrix|flyingtoasters|moebiusgears|fireworkx|hydrostat|hypertorus|jigsaw|juggler3d|kaleidocycle|kumppa|molecule|noof|polyhedra)$/ ) - effectsmsg( "overlay " + cmd ) - elsif ( cmd =~ /gltext|cowsay|xcowsay/ ) + 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( "overlay #{cmd} #{match[2]}" ) + effectsmsg( settings, "overlay #{cmd} #{match[2]}" ) puts "matched #{cmd} #{match[2]}" elsif ( sub ) if ( seconds > 20 ) seconds=20 end - effectsmsg( "overlay #{cmd} #{seconds} #{gltextargs[2]}" ) + effectsmsg( settings, "overlay #{cmd} #{seconds} #{gltextargs[2]}" ) puts "matched #{cmd} #{seconds} #{gltextargs[2]}" else if ( seconds > 5 ) seconds=5 end - effectsmsg( "overlay #{cmd} #{seconds} #{gltextargs[2]}" ) + effectsmsg( settings, "overlay #{cmd} #{seconds} #{gltextargs[2]}" ) puts "matched #{cmd} #{seconds} #{gltextargs[2]}" end elsif match[2]? && match[2] =~ /^.+$/ - effectsmsg( "overlay #{cmd} #{match[2]}" ) + effectsmsg( settings, "overlay #{cmd} #{match[2]}" ) puts "matched #{cmd} #{match[2]}" else - effectsmsg( "overlay #{cmd}" ) + effectsmsg( settings, "overlay #{cmd}" ) puts "failed to match gltext" end - elsif ( cmd =~ /cow|cowabunga|holycow/ ) + elsif ( cmd =~ /^(cow(s|)|cowabunga|holycow$)/ ) if match[2]? && match[2] =~ /^([0-9])+$/ - effectsmsg( "overlay bouncingcow #{match[2]}" ) + effectsmsg( settings, "overlay bouncingcow #{match[2]}" ) else - effectsmsg( "overlay bouncingcow" ) + effectsmsg( settings, "overlay bouncingcow" ) end elsif ( cmd == "overlay" ) if match[2]? && match[2] =~ /^[a-z]+$/ - ircmsg( settings["home"], settings["channel"], "| overlay requires an argument consisting wholly of lower case characters.") - effectsmsg( "overlay #{match[2]}" ) + ircipc.send( { "##{settings["channel"]}", "| overlay requires an argument consisting wholly of lower case characters."} ) + effectsmsg( settings, "overlay #{match[2]}" ) else end + elsif ( cmd =~ /^(system|dxdiag|computer)$/ ) + ircipc.send( { "##{settings["channel"]}", "| https://bungmonkey.omgwallhack.org/tmp/DxDiag.txt" } ) elsif ( cmd == "hackerman" ) - ircmsg( settings["home"], settings["channel"], "| https://bungmonkey.omgwallhack.org/img/hackerman.jpg" ) + 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=/ ) ) - ircmsg( settings["home"], settings["channel"], "| Lists are not accepted.\n" ) + ircipc.send( { "##{settings["channel"]}", "| Lists are not accepted.\n" } ) elsif Process.run( "sraddsong.sh", {match[2]} ) m = MPD::Client.new currentsong = m.currentsong @@ -493,9 +711,9 @@ bot.message( msgtuple[0], msgtuple[1][0..480] ) m.next end if ( status = m.status ) && ( playlistinfo = m.playlistinfo ) - ircmsg( settings["home"], settings["channel"], "| " + playlistinfo[ status["playlistlength"].to_i - 1 ]["file"].to_s + " added in position " + status["playlistlength"].to_s ) + ircipc.send( { "##{settings["channel"]}", "| " + playlistinfo[ status["playlistlength"].to_i - 1 ]["file"].to_s + " added in position " + status["playlistlength"].to_s } ) else - ircmsg( settings["home"], settings["channel"], "| A failure occured." ) + ircipc.send( { "##{settings["channel"]}", "| A failure occured." } ) end m.disconnect else @@ -503,9 +721,9 @@ bot.message( msgtuple[0], msgtuple[1][0..480] ) elsif ( cmd =~ /^current(|song)$/ ) m = MPD::Client.new if ( currentsong = m.currentsong ) - ircmsg( settings["home"], settings["channel"], "| Currently playing: " + currentsong["file"].to_s ) + ircipc.send( { "##{settings["channel"]}", "| Currently playing: " + currentsong["file"].to_s } ) else - ircmsg( settings["home"], settings["channel"], "| A failure occured." ) + ircipc.send( { "##{settings["channel"]}", "| A failure occured." } ) end m.disconnect elsif ( cmd =~ /^seek$/ ) @@ -515,25 +733,25 @@ bot.message( msgtuple[0], msgtuple[1][0..480] ) if ( ( musicstatus = m.status ) && ( pos = musicstatus["elapsed"].to_f.to_u64 ) ) min=( pos / 60 ).to_u64 sec=( pos % 60 ).to_u64 - ircmsg( settings["home"], settings["channel"], "| " + sprintf( "2%d:2%d", min, sec ) ) + ircipc.send( { "##{settings["channel"]}", "| " + sprintf( "2%d:2%d", min, sec ) } ) else - ircmsg( settings["home"], settings["channel"], "| An error occurred. " ) + ircipc.send( { "##{settings["channel"]}", "| An error occurred. " } ) end m.disconnect else - ircmsg( settings["home"], 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" ) + 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 ) - ircmsg( settings["home"], settings["channel"], "| Currently playing: " + currentsong["file"].to_s ) + ircipc.send( { "##{settings["channel"]}", "| Currently playing: " + currentsong["file"].to_s } ) else - ircmsg( settings["home"], settings["channel"], "| A failure occured." ) + ircipc.send( { "##{settings["channel"]}", "| A failure occured." } ) end else - ircmsg( settings["home"], settings["channel"], "| Playlist is now empty." ) + ircipc.send( { "##{settings["channel"]}", "| Playlist is now empty." } ) end m.disconnect elsif ( cmd == "followage" ) @@ -545,27 +763,27 @@ bot.message( msgtuple[0], msgtuple[1][0..480] ) puts client.user_id( args[0] ).to_s puts client.user_id( args[1] ).to_s puts json - ircmsg( settings["home"], settings["channel"], "| " + json["data"][0]["followed_at"].to_s ) + ircipc.send( { "##{settings["channel"]}", "| " + json["data"][0]["followed_at"].to_s } ) elsif args[0]? json = JSON.parse( client.get_user_follows( from: client.user_id( args[0] ).to_u64 , to: settings["channel_id"].to_u64 ) ) puts client.user_id( args[0] ).to_s puts json - ircmsg( settings["home"], settings["channel"], "| " + json["data"][0]["followed_at"].to_s ) + ircipc.send( { "##{settings["channel"]}", "| " + json["data"][0]["followed_at"].to_s } ) end else json = JSON.parse( client.get_user_follows( from: uid.to_u64 , to: settings["channel_id"].to_u64 ) ) puts json - ircmsg( settings["home"], settings["channel"], "| " + json["data"][0]["followed_at"].to_s ) + ircipc.send( { "##{settings["channel"]}", "| " + json["data"][0]["followed_at"].to_s } ) end rescue ex - ircmsg( settings["home"], settings["channel"], "An error occurred! " + ex.message.to_s ) + ircipc.send( { "##{settings["channel"]}", "An error occurred! " + ex.message.to_s } ) end end end end rooms = Array( String ).new - rooms = [ "#bungmonkey", "kr3wzz" ] + rooms = [ "##{settings["channel"]}" ] # Connect to Twitch bot.run( rooms.map{ | room | room.sub( /^#/, "") } ) |