#!/usr/bin/env ruby # APB/OBS-websocket scene management bridge require 'json' require 'socket' require 'openssl' require 'websocket' require 'net/http' require 'pp' confdir = Dir.home + '/.config/twitch/' ENV['APPDATA'] && confdir = ENV['APPDATA'] + "\\twitch\\" ENV['XDG_CONFIG_HOME'] && confdir = ENV['XDG_CONFIG_HOME'] + '/twitch/' access_token = IO.read( confdir + 'access_token' ).chomp channel = IO.read( confdir + 'channel' ).chomp client_id = IO.read( confdir + 'client_id' ).chomp unix_socket_path = confdir + 'chat_socket' obs_password = IO.read( confdir + 'obs_password' ).chomp def apbsocketwrite( unix_socket_path, text ) unix_socket = UNIXSocket.new( unix_socket_path ) unix_socket.print( text ) rescue ensure unix_socket.close end def websocketwrite( json ) $tcp_socket.write( WebSocket::Frame::Outgoing::Client.new( data: json, type: :text ).to_s ) rescue IOError, EOFError $tcp_socket.close websocket_create() retry end # the obsreader thread specially interprets unusual message-id-keyed responses; '1' will be a safe default. def SetSourceRender( source, render = 'true', id = '1' ) websocketwrite( '{ "request-type": "SetSourceRender", "message-id" : "' + id.to_s + '", "source" : "' + source + '", "render" : ' + render.to_s + ' }' ) end def GetCurrentScene( id = '1' ) websocketwrite( '{ "request-type": "GetCurrentScene", "message-id" : "' + id.to_s + '" }' ) end def GetSceneList( id = '1' ) websocketwrite( '{ "request-type": "GetSceneList", "message-id" : "' + id.to_s + '" }' ) end def SetCurrentScene( scene, id = '1' ) websocketwrite( '{ "request-type": "SetCurrentScene", "message-id" : "' + id.to_s + '", "scene-name": "' + scene + '" }' ) end def GetStreamingStatus( id = '1' ) websocketwrite( '{ "request-type": "GetStreamingStatus", "message-id" : "' + id.to_s + '" }' ) end def transientsource( source ) Thread.new { SetSourceRender( 'media-' + source , false ) sleep 0.1 SetSourceRender( 'media-' + source , true ) sleep 60 SetSourceRender( 'media-' + source , false ) } end #json["subscriptions"][0]["user"]["name"] def websocket_create() begin $tcp_socket = TCPSocket.new( '127.0.0.1', 4444 ) $tcp_socket.write( WebSocket::Handshake::Client.new( url: 'ws://localhost/' ).to_s ) IO.select( [ $tcp_socket ] ) print $tcp_socket.read_nonblock( 4096 ) websocketwrite( '{ "request-type": "GetAuthRequired", "message-id" : "1" }' ) # Let the obsreader thread do the actual authentication. rescue IOError, EOFError $tcp_socket.close sleep 1 retry end end websocket_create() frame = WebSocket::Frame::Incoming::Client.new frame_decoded = String.new status = Hash.new $lastworldenter = String.new def randomyoudied() if rand(5) > 1 transientsource( 'youdied' ) else transientsource( [ 'youdied-tiggs', 'youdied-nerf', 'youdied-thanksobama' ][rand( 3 )] ) end end def log_dispatch( line ) # 15:22:25 - APB_UI: UcUIAction_WorldEnter::Activated() LastPlayedCharacter = BungMonkey@3002 # 06:48:07 - APB_UI: Unrecognised tag "Color" in markup text "Krezz (+ Witherr ) BungoTheClown" (HUD Message AM_CombatKillOpp) # 07:51:34 - APB_UI: Unrecognised tag "Color" in markup text "Andromgzeda (+ Violetta94 ) Bung" (HUD Message AM_CombatKillOpp) # arg1 may be empty String if ( match = line.match( /^\d\d:\d\d:\d\d\s+-\s+APB_UI:\s+UcUIAction_WorldEnter::Activated\(\)\s+LastPlayedCharacter\s+=\s+([0-9a-zA-Z]+)@\d+/ ) ) $lastworldenter = match.to_a[1] puts "Last world enter updated: #{$lastworldenter}" elsif line =~ /\d\d:\d\d:\d\d\s+-\s+APB_UI:\s+Unrecognised\s+tag\s+"Color"\s+in\s+markup\s+text\s+"[0-9a-zA-Z]+<\/col>\s+\(\+\s+[0-9a-zA-Z]+\s+\)<\/col>\s+#{$lastworldenter}<\/col>"\s+\(HUD\s+Message\s+AM_CombatKillOpp\)/ puts 'You died!' randomyoudied() end end # apblog read loop event thread apbreader = Thread.new { begin lastline = String.new apblog = IO.popen('/usr/bin/tail -n0 -F /cygdrive/c/Program\ Files\ \(x86\)/Steam/SteamApps/common/APB\ Reloaded/APBGame/Logs/Current.log' ) # apblog = IO.popen('/usr/bin/tail -n0 -F /cygdrive/c/cygwin64/home/user/test.log' ) apblog.each_line do |line| if lastline != line # puts line log_dispatch( line ) lastline = line end end rescue ensure apblog.close end } # websocket read loop event thread #obsreader = Thread.new { begin while true frame << $tcp_socket.read_nonblock( 4096 ) while ( frame_decoded = frame.next.to_s ) != "" frame_parsed = JSON.parse(frame_decoded) if frame_parsed["update-type"] == "StreamStatus" status = frame_parsed elsif frame_parsed['current-scene'] && frame_parsed['scenes'] # GetSceneList text = '| Current scene: ' + frame_parsed['current-scene'] + ' | Available: ' + frame_parsed['scenes'].map{|v| v['name'] }.sort.join(' ') + "\n" print text #apbsocketwrite( unix_socket_path, text ) elsif frame_parsed['name'] && frame_parsed['sources'] # GetCurrentScene pp frame_parsed text = '| Sources: ' + frame_parsed['sources'].map{|v| v['name'] }.sort.join(' ') + "\n" print text if frame_parsed['message-id'] == '1' #apbsocketwrite( unix_socket_path, text ) else # other message-ids are a source we want to toggle frame_parsed['sources'].each do |v| if v['name'] == frame_parsed['message-id'] if v['render'] == true SetSourceRender( v['name'], 'false', '2' ) #apbsocketwrite( unix_socket_path, '| Source ' + v['name'] + " is now unrendered\n" ) elsif v['render'] == false SetSourceRender( v['name'], 'true', '2' ) #apbsocketwrite( unix_socket_path, '| Source ' + v['name'] + " is now visible\n" ) end end end end elsif frame_parsed['update-type'] == 'SwitchScenes' #apbsocketwrite( unix_socket_path, '| Scene is now ' + frame_parsed['scene-name'] + "\n" ) #elsif frame_parsed['update-type'] == 'SceneItemVisibilityChanged' # #apbsocketwrite( unix_socket_path, '| Source ' + frame_parsed['item-name'] + ' visibility is now ' + frame_parsed['item-visible'].to_s + "\n" ) elsif frame_parsed['authRequired'] == true # GetAuthRequired websocketwrite( '{ "request-type": "Authenticate", "message-id" : "2", "auth" : "' + Digest::SHA256.base64digest(Digest::SHA256.base64digest(obs_password + frame_parsed['salt'])+frame_parsed['challenge']) + '" }' ) else print frame_decoded end end #pp JSON.parse(frame_decoded) end rescue IO::WaitReadable, IO::EAGAINWaitReadable, JSON::ParserError IO.select([$tcp_socket]) retry rescue IOError, EOFError $tcp_socket.close websocket_create() retry end #}