#!/usr/bin/env ruby require 'curses' require 'io/console' require 'socket' # $ gem install libnotify require 'libnotify' module ColorPair DEFAULT = 0 TITLE = 1 end def io_readable?(socket) read_array = IO::select [socket], nil, nil, 0.0 if read_array && read_array[0] read_array[0].include? socket else false end end def info_from_str(str) arr = str.split(',') { :ukn1 => arr[0].to_i, :ukn2 => arr[1].to_i, :ukn3 => arr[2].to_i, :user_id => arr[3].to_i, :user_name => arr[4], :battle_id => arr[5].to_i, :version => arr[6].to_i, :hostname => arr[7], :in_battle? => arr[8] == "0", :ukn4 => arr[9].to_i, :last_active => Time.now } end def draw_title_color Curses::setpos(0, 0) Curses::attron(Curses::color_pair(ColorPair::TITLE)) do Curses::addstr(" " * Curses::cols) end end def draw_title(title) Curses::setpos(0, 0) Curses::attron(Curses::color_pair(ColorPair::TITLE)|Curses::A_BOLD) do Curses::addstr(" " + title) end end def draw_player(player, addr) player_str = "- #{player[:last_active].strftime("%Y-%m-%d %H:%M:%S").ljust(19)} #{addr.ljust(15)} #{player[:in_battle?] ? "In battle" : "Idling "} #{player[:hostname]} #{player[:user_name]}\n" Curses::addstr(player_str) end players = {} if __FILE__ == $0 begin Curses::init_screen Curses::start_color Curses::init_pair(ColorPair::TITLE, Curses::COLOR_WHITE, Curses::COLOR_MAGENTA) Curses::use_default_colors Curses::clear draw_title_color Curses::refresh prog_start = Time.now socket = UDPSocket.new socket.bind "0.0.0.0", 1228 loop do while io_readable?(socket) data, addr = socket.recvfrom 1024 addr = addr[3] player_info = info_from_str(data) if Time.now - prog_start > 40.0 && !players.key?(addr) # We notify new players starting from 40s after the program # start (so that we detect all players that were already # playing before notifying) Libnotify.show(body: "#{player_info[:user_name]} started playing", summary: "Hearthstone", timeout: 3.0) end players[addr] = player_info end Curses::clear draw_title_color draw_title "Hearthstone connected players" Curses::setpos(1, 0) players.each { |addr,p| Libnotify.show(body: "#{p[:user_name]} stopped playing", summary: "Hearthstone", timeout: 3.0) if Time.now - p[:last_active] > 60.0 } players.delete_if{ |addr,p| Time.now - p[:last_active] > 60.0 } players.sort {|p1,p2| p1[1][:user_name] <=> p2[1][:user_name]}.to_h.each do |addr,player_info| draw_player(player_info, addr) end Curses::refresh sleep 1 end rescue Interrupt ensure Curses::close_screen end end