require "pretty_print" require "json" require "openssl/hmac" require "obswebsocket" puts "Content-type: text/plain; charset=UTF-8" puts settings = Hash(String, String).new ["eventsub_secret", "channel"].each do |key| begin settings[key] = File.read( "/etc/twitch/" + key ).chomp rescue STDERR.puts "Warning: Missing /etc/twitch/" + key end end settings["configdir"] = "/home/jrayhawk/.config/bungmobott/" 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/" ) Dir.mkdir_p( settings["configdir"] ) # enable effects? effects = Hash( String, String ).new if File.exists?( settings["configdir"] + "/effects.txt" ) File.each_line( settings["configdir"] + "/effects.txt" ) do |line| effects[ line.downcase ] = line end end explosioncount = 0 youdied = false def obstemporarymediacreate( obs : OBS::WebSocket, 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 ) end def obsrandommediaenable( obs : OBS::WebSocket, siname : String ) if ( Random.rand(3) < 2 ) obs.scenes.current.metascene[siname][0].enable! else randsiname = obs.scenes.current.metascene.keys.select( /^#{siname}/ ).sample( 1 )[0] obs.scenes.current.metascene[randsiname][0].enable! end end def ircmsg( channel : String, msg : String) if msg !~ /(\r|\n)/ sock = Socket.unix sock.connect Socket::UNIXAddress.new( "/etc/twitch/irc-socket".to_s ) sock.puts( "#" + channel + " " + "eventsub: " + msg ) sock.close end end def effectsmsg( msg : String ) sock = Socket.unix sock.connect Socket::UNIXAddress.new( "/etc/twitch/effects-socket" ) sock.puts( msg ) sock.close rescue ex STDERR.puts( ex ) end struct Nil def as_s? self end def as_i? self end end File.open("/tmp/eventsub.#{Process.pid}.txt", "w", 0o644 ) do |file| input = STDIN.gets_to_end begin json = JSON.parse( input ) digest = "sha256=" + OpenSSL::HMAC.hexdigest( :sha256, settings["eventsub_secret"], ENV["HTTP_TWITCH_EVENTSUB_MESSAGE_ID"] + ENV["HTTP_TWITCH_EVENTSUB_MESSAGE_TIMESTAMP"] + input ) if json["challenge"]? && ( ENV["HTTP_TWITCH_EVENTSUB_MESSAGE_SIGNATURE"] == digest ) file.puts( json["challenge"] ) elsif json["event"]? case json["subscription"]["type"] when "channel.update" #ircmsg( settings["channel"], "#{json["event"]["broadcaster_user_login"]} is now playing #{json["event"]["category_name"]} with the title #{json["event"]["title"]}" ) effectsmsg( "overlay gltext #{json["event"]["broadcaster_user_login"]} is now playing #{json["event"]["category_name"]} with the title #{json["event"]["title"]}" ) when "stream.online" ircmsg( settings["channel"], "| #{json["event"]["broadcaster_user_login"]} is now streaming!" ) when "stream.offline" ircmsg( settings["channel"], "| #{json["event"]["broadcaster_user_login"]} is now offline." ) when "channel.follow" ircmsg( settings["channel"], "#{json["event"]["user_login"]} is now following!" ) when "channel.ban" ircmsg( settings["channel"], "#{json["event"]["user_login"]} is now banned!" ) youdied = true when "channel.unban" ircmsg( settings["channel"], "#{json["event"]["user_login"]} is now unbanned!" ) when "channel.raid" effectsmsg( "overlay gltext #{json["event"]["from_broadcaster_user_login"]} is raiding with #{json["event"]["viewers"]} viewer(s)!" ) ircmsg( settings["channel"], "#{json["event"]["from_broadcaster_user_login"]} is raiding with #{json["event"]["viewers"]} viewer(s)! bungmoBlobDance bungmoBlobDance bungmoBlobDance bungmoBlobDance" ) when "channel.subscribe" sub_recver = ( json["event"]["user_login"]?.as_s? || "anonymous" ) sub_broadcaster = ( json["event"]["broadcaster_user_login"]?.as_s? || "unknown" ) sub_plan = ( json["event"]["tier"]?.as_s? || "unknown" ) #sub_months = ( json["event"]["cumulative_months"]?.as_i? || 0 ) unless json["event"]["is_gift"].as_bool # If gift, let count get size-limited in channel.subscription.gift below instead explosioncount += 5 end ircmsg( settings["channel"], sub_recver.to_s + " has subscribed at sub level #" + sub_plan.to_s + " bungmoBlobDance bungmoBlobDance bungmoBlobDance bungmoBlobDance" ) when "channel.subscription.gift" sub_sender = ( json["event"]["user_login"]?.as_s? || "anonymous" ) sub_recver = ( json["event"]["broadcaster_user_login"]?.as_s? || sub_sender ) sub_plan = ( json["event"]["tier"]?.as_s? || "unknown" ) sub_total = ( json["event"]["total"]?.as_i? || 0 ) sub_cumulative = ( json["event"]["cumulative_total"]?.as_i? || 0 ) effectsmsg( "overlay gltext 10 #{sub_sender} donated #{sub_total} subs!" ) ircmsg( settings["channel"], "#{sub_sender} has gifted #{sub_total} tier #{sub_plan} subscription for a total of #{sub_cumulative}" ) spawn do count = sub_total * 5 ( count > 50 ) && ( count = 50 ) explosioncount += count end when "channel.subscription.message" sub_sender = ( json["event"]["user_login"]?.as_s? || "anonymous" ) sub_plan = ( json["event"]["tier"]?.as_s? || "unknown" ) sub_months = ( json["event"]["cumulative_months"]?.as_i? || 0 ) sub_msg = ( json["event"]["message"]["text"]?.as_s? || "" ) effectsmsg( "overlay gltext 10 " + sub_sender + " subscribed!" ) ircmsg( settings["channel"], sub_sender.to_s + " has subscribed for " + sub_months.to_s + " months at sub level #" + sub_plan.to_s + ": " + sub_msg.to_s ) end else STDOUT.puts( json.pretty_inspect ) STDOUT.puts( ARGV.pretty_inspect ) STDOUT.puts( ENV.pretty_inspect ) STDOUT.puts( file.path ) end file.puts( json.pretty_inspect ) file.puts( ARGV.pretty_inspect ) file.puts( ENV.pretty_inspect ) rescue ex STDERR.puts( ex ) STDOUT.puts( input ) file.puts( input ) end end if youdied obs = OBS::WebSocket.new( "ws://127.0.0.1:4455/", retry: false ) obsrandommediaenable( obs, "media-youdied" ) end if ( explosioncount > 0 ) obs = OBS::WebSocket.new( "ws://127.0.0.1:4455/", retry: false ) effects.values.select(/^explosions/).sample( explosioncount ).each do | effect | obstemporarymediacreate( obs, "meta-foreground", effect.gsub(/[\/ ]/,"_"), "C:/cygwin64/home/user/effects/#{effect}") end end exit #{"subscription" => # {"id" => "3c1c3a9c-748e-4374-8e61-2c0210755af3", # "status" => "enabled", # "type" => "channel.subscribe", # "version" => "1", # "condition" => {"broadcaster_user_id" => "59895482"}, # "transport" => # {"method" => "webhook", # "callback" => "https://twitch.fairlystable.org/eventsub"}, # "created_at" => "2021-02-09T19:13:49.618252874Z", # "cost" => 0}, # "event" => # {"user_id" => "71506453", # "user_login" => "shift_f7", # "user_name" => "Shift_F7", # "broadcaster_user_id" => "59895482", # "broadcaster_user_login" => "bungmonkey", # "broadcaster_user_name" => "BungMonkey", # "tier" => "1000", # "is_gift" => true}} #