diff --git a/app/models/etf2l_profile.rb b/app/models/etf2l_profile.rb new file mode 100644 index 000000000..685c954dc --- /dev/null +++ b/app/models/etf2l_profile.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class Etf2lProfile + attr_accessor :json + + def initialize(profile_body) + @json = JSON.parse(profile_body) + end + + def name + json.dig('player', 'name') + end + + def banned? + active_bans.any? + end + + def ban_reason + return nil if active_bans.none? + + active_bans.map { |b| b['reason'] }.join(', ') + end + + def ban_expires_at + return nil if active_bans.none? + + active_bans.map { |b| Time.at(b['end']).to_date }.join(', ') + end + + def self.fetch(steam_uid) + response_body = Etf2lApi.profile(steam_uid) + new(response_body) if response_body + end + + private + + def active_bans + @active_bans ||= begin + now = Time.now.to_i + bans = json.dig('player', 'bans') + if bans&.any? + bans.reject { |b| now > b['end'].to_i } + else + [] + end + end + end +end diff --git a/app/models/league_ban.rb b/app/models/league_ban.rb new file mode 100644 index 000000000..837f358f6 --- /dev/null +++ b/app/models/league_ban.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class LeagueBan + def self.fetch(steam_uid) + klass = if SITE_HOST == 'serveme.tf' + Etf2lProfile + elsif SITE_HOST == 'na.serveme.tf' + RglProfile + end + + return unless klass + + profile = klass.fetch(steam_uid) + profile.banned? && profile + end +end diff --git a/app/models/reservation_player.rb b/app/models/reservation_player.rb index a8e2d9fe6..ec1ee8ac4 100644 --- a/app/models/reservation_player.rb +++ b/app/models/reservation_player.rb @@ -44,6 +44,10 @@ def self.banned_ip?(ip) banned_ranges.any? { |range| range.include?(ip) } end + def self.banned_league_uid?(steam_id64) + LeagueBan.fetch(steam_id64)&.banned? + end + def self.banned_uids @banned_uids ||= CSV.read(Rails.root.join('doc', 'banned_steam_ids.csv'), headers: true).to_h { |row| [row['steam_id64'].to_i, row['reason']] } end diff --git a/app/models/rgl_profile.rb b/app/models/rgl_profile.rb new file mode 100644 index 000000000..a0338dc1f --- /dev/null +++ b/app/models/rgl_profile.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class RglProfile + attr_accessor :json + + def initialize(profile_body) + @json = JSON.parse(profile_body) + end + + def name + json['name'] + end + + def banned? + json.dig('status', 'isBanned') == true + end + + def ban_reason + reason = json.dig('banInformation', 'reason') + ActionView::Base.full_sanitizer.sanitize(reason) if reason + end + + def ban_expires_at + expires_at = json.dig('banInformation', 'endsAt') + expires_at && Date.parse(expires_at) + end + + def self.fetch(steam_uid) + response_body = RglApi.profile(steam_uid) + new(response_body) if response_body + end +end diff --git a/app/services/etf2l_api.rb b/app/services/etf2l_api.rb new file mode 100644 index 000000000..408c6daed --- /dev/null +++ b/app/services/etf2l_api.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class Etf2lApi + def self.profile(steam_uid) + cached_profile_response(steam_uid) || fetch_profile(steam_uid) + end + + def self.cached_profile_response(steam_uid) + Rails.cache.read("etf2l_profile_#{steam_uid}") + end + + def self.fetch_profile(steam_uid) + response = etf2l_connection.get("player/#{steam_uid}") + return unless response.success? + + Rails.cache.write("etf2l_profile_#{steam_uid}", response.body, expires_in: 1.day) + + response.body + end + + def self.etf2l_connection + Faraday.new(url: 'https://api-v2.etf2l.org/') + end +end diff --git a/app/services/rgl_api.rb b/app/services/rgl_api.rb new file mode 100644 index 000000000..12339ab45 --- /dev/null +++ b/app/services/rgl_api.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class RglApi + def self.profile(steam_uid) + cached_profile_response(steam_uid) || fetch_profile(steam_uid) + end + + def self.cached_profile_response(steam_uid) + Rails.cache.read("rgl_profile_#{steam_uid}") + end + + def self.fetch_profile(steam_uid) + response = rgl_connection.get("v0/profile/#{steam_uid}") + return unless response.success? || response.status == 404 + + Rails.cache.write("rgl_profile_#{steam_uid}", response.body, expires_in: 1.day) + + response.body + end + + def self.rgl_connection + Faraday.new(url: 'https://api.rgl.gg/') + end +end diff --git a/app/workers/log_worker.rb b/app/workers/log_worker.rb index ed2fc062d..8804f6a9a 100644 --- a/app/workers/log_worker.rb +++ b/app/workers/log_worker.rb @@ -67,6 +67,8 @@ def handle_connect elsif (ReservationPlayer.banned_uid?(community_id) || ReservationPlayer.banned_ip?(ip)) && !ReservationPlayer.whitelisted_uid?(community_id) reservation.server.rcon_exec "banid 0 #{event.player.steam_id} kick; addip 0 #{ip}" Rails.logger.info "Removed banned player with UID #{community_id}, IP #{event.message}, name #{event.player.name}, from reservation #{reservation_id}" + elsif (banned_league_profile = LeagueBan.fetch(community_id)) + Rails.logger.info "League banned player with UID #{community_id}, IP #{event.message}, name #{event.player.name} connected to reservation #{reservation_id}: #{banned_league_profile.ban_reason}" elsif reservation.server.supports_mitigations? AllowReservationPlayerWorker.perform_async(rp.id) end