diff options
Diffstat (limited to 'ircobsbridge.rb')
-rw-r--r-- | ircobsbridge.rb | 204 |
1 files changed, 149 insertions, 55 deletions
diff --git a/ircobsbridge.rb b/ircobsbridge.rb index ace7de7..103c7a2 100644 --- a/ircobsbridge.rb +++ b/ircobsbridge.rb @@ -8,19 +8,23 @@ require 'openssl' require 'websocket' require 'net/http' require 'pp' +require 'uri' 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 +# returning 'true' on each line to conceal results when sourcing in irb -def ircsocketwrite( unix_socket_path, text ) - unix_socket = UNIXSocket.new( unix_socket_path ) +( $ACCESS_TOKEN = IO.read( confdir + 'access_token' ).chomp ) && true +( $CHANNEL = IO.read( confdir + 'channel' ).chomp ) && true +( $CLIENT_ID = IO.read( confdir + 'client_id' ).chomp ) && true +( $UNIX_SOCKET_PATH = confdir + 'chat_socket' ) && true +( $OBS_PASSWORD = IO.read( confdir + 'obs_password' ).chomp ) && true +( $TRN_API_KEY = IO.read( confdir + '../trn-api-key' ).chomp ) && true + +def ircsocketwrite( text ) + unix_socket = UNIXSocket.new( $UNIX_SOCKET_PATH ) unix_socket.print( text ) rescue ensure @@ -29,7 +33,7 @@ end def websocketwrite( json ) $tcp_socket.write( WebSocket::Frame::Outgoing::Client.new( data: json, type: :text ).to_s ) -rescue IOError, EOFError +rescue IOError, EOFError, Errno::ECONNRESET $tcp_socket.close websocket_create() retry @@ -57,6 +61,84 @@ def GetStreamingStatus( id = '1' ) websocketwrite( '{ "request-type": "GetStreamingStatus", "message-id" : "' + id.to_s + '" }' ) end +def pubgstats( name ) + tries = 2 + http = Net::HTTP.new( 'pubgtracker.com', 443 ) + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + http.read_timeout = 500 + + req = Net::HTTP::Get.new( "/api/profile/pc/#{name}" ) + ( req["TRN-Api-Key"] = $TRN_API_KEY ) && true + + begin + response = JSON.parse( http.request( req ).body ) + rescue + sleep 2 + retry unless( tries-=1 ).zero? + end + + if response['error'] then + ircsocketwrite( "| pubgstats: #{response['message']} (maybe bad name?)\n" ) + return + end + + totalwins = 0 + totallosses = 0 + totalkills = 0 + totaldeaths = 0 + totalrounds = 0 + bestrank = 9999999 + bestrating = 0 + bestroundkills = 0 + bestrangekill = 0 + +#response['Stats'].each { |matchstats| matchstats['Stats'].each { |stats| printf( "%9s %3s %5s %25s %10s\n", matchstats['Season'], matchstats['Region'], matchstats['Match'], stats['field'], stats['value'] ) } } && true# debug +#response['Stats'].each { |matchstats| matchstats['Stats'].each { |stats| printf( "%9s %3s %5s %23s %20s %12s %10s %10s %10s %10s\n", matchstats['Season'], matchstats['Region'], matchstats['Match'], stats['label'], stats['field'], stats['category'], stats['value'], stats['rank'], stats['percentile'], stats['displayValue'] ) } } && true # debug + +response['Stats'].each do |matchstats| + kdr = 0.0 + matchstats['Stats'].each do |stats| + if stats['field'] == 'KillDeathRatio' then + kdr = stats['value'].to_f + end + stats['field'] == 'Wins' && totalwins += stats['value'].to_i + stats['field'] == 'Losses' && totallosses += stats['value'].to_i + stats['field'] == 'RoundsPlayed' && totalrounds += stats['value'].to_i + if stats['field'] == 'Kills' && kdr != 0 then + totalkills += stats['value'].to_i + totaldeaths += stats['value'].to_i / kdr + end + ( stats['field'] == 'BestRating' && stats['value'].to_i > bestrating ) && bestrating = stats['value'].to_i + ( stats['field'] == 'RoundMostKills' && stats['value'].to_i > bestroundkills ) && bestroundkills = stats['value'].to_i + ( stats['field'] == 'LongestKill' && stats['value'].to_i > bestrangekill ) && bestrangekill = stats['value'].to_i + ( stats['field'] == 'BestRank' && 0 < stats['value'].to_i && stats['value'].to_i < bestrank ) && bestrank = stats['value'].to_i + end + +end && true + + +#bestrank +#bestrating +#bestroundkills +totalkills /= 2 +totaldeaths /= 2 +totalwins /= 2 +totallosses /= 2 +#bestrangekill +winratio = totalwins.to_f/totallosses.to_f +killratio = totalkills.to_f/totaldeaths.to_f + +ircsocketwrite( sprintf( "| %s BEST: rating: %i, rank: %i, roundkills: %i, rangekill: %i; TOTAL: kills/deaths: %i / %i = %02.03f, wins/losses: %i / %i = %01.03f ", response['PlayerName'], bestrating, bestrank, bestroundkills, bestrangekill, totalkills, totaldeaths, killratio, totalwins, totallosses, winratio ) ) +#ircsocketwrite( "| #{response['PlayerName']} PUBG Stats: BestRating: #{bestrating}, BestRank: #{bestrank}, HighestRoundKills: #{bestroundkills}, TotalKills: #{totalkills}, TotalWins/Losses #{totalwins}/#{totallosses} = #{winratio}, HighestRange: #{bestrangekill}\n" ) + +# optimally: +# highest 'Kills' 'KillDeathRatio' & percentile 'Wins'/'WinPoints'/'WinRatio'/percentile 'RoundMostKills' 'RoundsPlayed' 'BestRating' 'BestRank' 'LongestKill' +# realistically: +# BestRank BestRating RoundsPlayed Wins/Losses WinRatio Kills RoundMostKills LongestKill + +end + def transientsource( source ) Thread.new { SetSourceRender( 'media-' + source , false ) @@ -77,23 +159,19 @@ def websocket_create() 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 + rescue IOError, EOFError, Errno::ECONNREFUSED, Errno::ECONNRESET + $tcp_socket && $tcp_socket.close + sleep 5 retry end end -websocket_create() -frame = WebSocket::Frame::Incoming::Client.new -frame_decoded = String.new -status = Hash.new $lastused = Hash.new $globallastused = 0 def command_dispatch( mode, user, command, arg1 ) # arg1 may be empty String - safe = /^[-0-9a-zA-Z]+$/ + safe = /^[-0-9a-zA-Z_]+$/ if mode == '@' && command == 'scene' if arg1 =~ safe SetCurrentScene( arg1 ) @@ -113,10 +191,17 @@ def command_dispatch( mode, user, command, arg1 ) return end elsif mode == '@' && command == 'commands' - ircsocketwrite( unix_socket_path, "!source !scene !metaminute !nopgl1 !nopgl2\n" ) + ircsocketwrite( "| !source !scene !pubg !metaminute !nopgl1 !nopgl2\n" ) return end # ratelimited commands after +o commands + if command =~ /^(pubg)$/ + if ( ! $lastused[ command ] || $lastused[ command ] <= ( Time.now.to_i - 5 ) ) # || mode == '@' + $lastused[ command ] = Time.now.to_i + pubgstats( arg1 ) + return + end + end if ( ! $lastused[ command ] || $lastused[ command ] <= ( Time.now.to_i - 120 ) ) && $globallastused <= ( Time.now.to_i - 60 ) # || mode == '@' $globallastused = Time.now.to_i $lastused[ command ] = Time.now.to_i @@ -124,18 +209,55 @@ def command_dispatch( mode, user, command, arg1 ) transientsource( command ) return elsif command == 'commands' - ircsocketwrite( unix_socket_path, "!metaminute !nopgl1 !nopgl2\n" ) + ircsocketwrite( "| !pubg !metaminute !nopgl1 !nopgl2\n" ) return end end end +def frame_dispatch( frame ) + if frame["update-type"] == "StreamStatus" + $status = frame + elsif frame['current-scene'] && frame['scenes'] # GetSceneList + text = '| Current scene: ' + frame['current-scene'] + ' | Available: ' + frame['scenes'].map{|v| v['name'] }.sort.join(' ') + "\n" + print text + ircsocketwrite( text ) + elsif frame['name'] && frame['sources'] # GetCurrentScene + pp frame + text = '| Sources: ' + frame['sources'].map{|v| v['name'] }.sort.join(' ') + "\n" + print text + if frame['message-id'] == '1' + ircsocketwrite( text ) + else # other message-ids are a source we want to toggle + frame['sources'].each do |v| + if v['name'] == frame['message-id'] + if v['render'] == true + SetSourceRender( v['name'], 'false', '2' ) + ircsocketwrite( '| Source ' + v['name'] + " is now unrendered\n" ) + elsif v['render'] == false + SetSourceRender( v['name'], 'true', '2' ) + ircsocketwrite( '| Source ' + v['name'] + " is now visible\n" ) + end + end + end + end + elsif frame['update-type'] == 'SwitchScenes' + ircsocketwrite( '| Scene is now ' + frame['scene-name'] + "\n" ) + #elsif frame['update-type'] == 'SceneItemVisibilityChanged' + # ircsocketwrite( '| Source ' + frame['item-name'] + ' visibility is now ' + frame['item-visible'].to_s + "\n" ) + elsif frame['authRequired'] == true # GetAuthRequired + websocketwrite( '{ "request-type": "Authenticate", "message-id" : "2", "auth" : "' + Digest::SHA256.base64digest(Digest::SHA256.base64digest($OBS_PASSWORD + frame['salt'])+frame['challenge']) + '" }' ) + else + print frame + end +end + # irclog read loop event thread ircreader = Thread.new { begin - irclog = IO.popen('/usr/bin/tail -n0 -F ' + ENV['HOME'] + '/irclogs/twitch/#' + channel + '.log' ) + irclog = IO.popen('/usr/bin/tail -n0 -F ' + ENV['HOME'] + '/irclogs/twitch/#' + $CHANNEL + '.log' ) irclog.each_line do |line| - if ( match = line.match(/^\d\d:\d\d <([@ +])(.+)?> *!([-0-9a-zA-Z]+) *([-0-9a-zA-Z]*)/) ) + if ( match = line.match(/^\d\d:\d\d <([@ +])(.+)?> *!([-0-9a-zA-Z]+) *([-0-9a-zA-Z_]*)/) ) command_dispatch( *match.to_a[1..4] ) end end @@ -145,6 +267,11 @@ ensure end } +websocket_create() +frame = WebSocket::Frame::Incoming::Client.new +frame_decoded = String.new +$status = Hash.new + # websocket read loop event thread obsreader = Thread.new { begin @@ -152,47 +279,14 @@ obsreader = Thread.new { 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 - ircsocketwrite( 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' - ircsocketwrite( 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' ) - ircsocketwrite( unix_socket_path, '| Source ' + v['name'] + " is now unrendered\n" ) - elsif v['render'] == false - SetSourceRender( v['name'], 'true', '2' ) - ircsocketwrite( unix_socket_path, '| Source ' + v['name'] + " is now visible\n" ) - end - end - end - end - elsif frame_parsed['update-type'] == 'SwitchScenes' - ircsocketwrite( unix_socket_path, '| Scene is now ' + frame_parsed['scene-name'] + "\n" ) - #elsif frame_parsed['update-type'] == 'SceneItemVisibilityChanged' - # ircsocketwrite( 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 + frame_dispatch( frame_parsed ) end #pp JSON.parse(frame_decoded) end rescue IO::WaitReadable, IO::EAGAINWaitReadable, JSON::ParserError IO.select([$tcp_socket]) retry - rescue IOError, EOFError + rescue IOError, EOFError, Errno::ECONNRESET $tcp_socket.close websocket_create() retry |