~pixelinc/gmod-checker.cr

e9820d2339fe1e55a5ba15a1a7b718520ddc8deb — Zac Nowicki 2 years ago 17ccd0a
Use z64/steam_id (#24)

This replaces our Steam::ID implementation with the faster and more
well-tested library I wrote. I kept our original specs to prove it still
has the same behavior, while documenting the mutations we need to make
for wrong Steam IDs.
6 files changed, 54 insertions(+), 78 deletions(-)

M shard.yml
M spec/id_spec.cr
D src/id.cr
M src/mappings.cr
M src/steam.cr
M src/web.cr
M shard.yml => shard.yml +2 -0
@@ 15,3 15,5 @@ dependencies:
    github: jeromegn/slang
  pg:
    github: will/crystal-pg
  steam_id:
    github: z64/steam_id

M spec/id_spec.cr => spec/id_spec.cr +27 -10
@@ 1,19 1,19 @@
require "./spec_helper"

record IDStub, id_64 : Int64, id_32 : String, id_3 : String
record IDStub, id_64 : UInt64, id_32 : String, id_3 : String

describe Steam::ID do
  id_stubs = {
    IDStub.new(
      76561198072216199_i64,
      76561198072216199,
      "STEAM_0:1:55975235",
      "[U:1:111950471]"),
    IDStub.new(
      76561197960361544_i64,
      76561197960361544,
      "STEAM_0:0:47908",
      "[U:1:95816]"),
    IDStub.new(
      76561198085325954_i64,
      76561198085325954,
      "STEAM_0:0:62530113",
      "[U:1:125060226]"),
  }


@@ 21,34 21,51 @@ describe Steam::ID do
  it "parses ID 32 format" do
    id_stubs.each do |data|
      id = Steam::ID.new(data.id_32)
      id.to_steam_64.should eq data.id_64

      # Universe is wrong:
      id.universe = :public

      # ID 32 does not include instance or account type:
      id.instance = 1
      id.account_type = :individual

      id.to_u64.should eq data.id_64
    end
  end

  it "parses ID 3 format" do
    id_stubs.each do |data|
      id = Steam::ID.new(data.id_3)
      id.to_steam_64.should eq data.id_64

      # ID 3 does not include universe or instance:
      id.universe = :public
      id.instance = 1

      id.to_u64.should eq data.id_64
    end
  end

  it "serializes ID 32 format" do
    id_stubs.each do |data|
      id = Steam::ID.new(data.id_64)
      id.to_steam_32.should eq data.id_32

      # Universe is wrong:
      id.universe = :individual

      id.to_s(Steam::ID::Format::Default).should eq data.id_32
    end
  end

  it "serializes ID 3 format" do
    id_stubs.each do |data|
      id = Steam::ID.new(data.id_64)
      id.to_steam_3.should eq data.id_3
      id.to_s(Steam::ID::Format::Community32).should eq data.id_3
    end
  end

  describe "#initialize" do
    it "raises with an unknown ID format" do
      expect_raises(Steam::ID::Error, "Unsupported ID format: foo") do
      expect_raises(Steam::ID::Error) do
        Steam::ID.new("foo")
      end
    end


@@ 56,7 73,7 @@ describe Steam::ID do

  it ".new(pull_parser)" do
    parser = JSON::PullParser.new(%("0"))
    expected = Steam::ID.new(0_i64)
    expected = Steam::ID.new(0)
    Steam::ID.new(parser).should eq expected
  end
end

D src/id.cr => src/id.cr +0 -61
@@ 1,61 0,0 @@
# Type representing Steam IDs. Can be used to convert an ID from one
# Steam ID format to another.
struct Steam::ID
  class Error < Exception
  end

  # Pattern for 32 bit Steam IDs
  STEAM_ID_32_REGEXP = /^STEAM_([0-1]:[0-1]:[0-9]+)$/

  # Pattern for Steam ID 3
  STEAM_ID_3_REGEXP = /^\[U:([0-1]:[0-9]+)\]$/

  @value : Int64

  def self.new(parser : JSON::PullParser)
    value = parser.read_string.to_i64
    new(value)
  end

  def initialize(id : String)
    if id =~ STEAM_ID_32_REGEXP
      universe, low, high = $1.split(':').map &.to_i64
      universe = 1_i64
      @value = (universe << 56) | (1_i64 << 52) | (1_i64 << 32) | (high << 1) | low
    elsif id =~ STEAM_ID_3_REGEXP
      universe, high = $1.split(':').map &.to_i64
      universe = 1_i64
      @value = (universe << 56) | (1_i64 << 52) | (1_i64 << 32) | high
    else
      raise Error.new("Unsupported ID format: #{id}")
    end
  end

  def initialize(@value : Int64)
  end

  def to_json(builder : JSON::Builder)
    builder.string @value.to_s
  end

  # The ID in 64 bit format
  def to_steam_64
    @value
  end

  # The ID in ID 32 format
  def to_steam_32
    # universe = (@value >> 56) & ((1_i64 << 8) - 1_i64)
    id = @value & ((1_i64 << 32) - 1_i64)
    low = id & 1
    high = (id >> 1) & ((1_i64 << 31) - 1_i64)
    "STEAM_0:#{low}:#{high}"
  end

  # The ID in ID 3 format
  def to_steam_3
    universe = (@value >> 56) & ((1_i64 << 8) - 1_i64)
    id = @value & ((1_i64 << 32) - 1_i64)
    "[U:#{universe}:#{id}]"
  end
end

M src/mappings.cr => src/mappings.cr +1 -1
@@ 25,7 25,7 @@ module Steam
      builder.object do
        builder.string "steamid"
        id.to_json(builder)
        builder.field "id_32", id.to_steam_32
        builder.field "id_32", id.to_s(Steam::ID::Format::Default)
        builder.field "personaname", persona_name
        builder.field "avatarfull", avatar
        builder.field "profileurl", profile_url

M src/steam.cr => src/steam.cr +15 -4
@@ 1,7 1,18 @@
require "http/client"
require "./id"
require "steam_id"
require "./mappings"

struct Steam::ID
  def self.new(parser : JSON::PullParser)
    value = parser.read_string.to_u64
    new(value)
  end

  def to_json(builder : JSON::Builder)
    builder.string @value.to_s
  end
end

class Steam::Client
  BASE_URL = "http://api.steampowered.com"



@@ 31,7 42,7 @@ class Steam::Client

  def get_players(player_ids : Array(ID)) : Array(Player)
    query = HTTP::Params.build do |form|
      form.add "steamids", player_ids.map { |id| id.to_steam_64 }.join(',')
      form.add "steamids", player_ids.map { |id| id.to_u64 }.join(',')
    end
    response = request("/ISteamUser/GetPlayerSummaries/v0002?#{query}")
    parse(Array(Player), from: response, in: "players")


@@ 39,12 50,12 @@ class Steam::Client

  def get_lender_id(player_id : ID)
    query = HTTP::Params.build do |form|
      form.add "steamid", player_id.to_steam_64.to_s
      form.add "steamid", player_id.to_u64.to_s
      form.add "appid_playing", "4000"
      form.add "format", "json"
    end
    response = request("/IPlayerService/IsPlayingSharedGame/v0001?#{query}")
    value = parse(String, from: response, in: "lender_steamid")
    ID.new(value.to_i64) unless value == "0"
    ID.new(value.to_u64) unless value == "0"
  end
end

M src/web.cr => src/web.cr +9 -2
@@ 49,7 49,11 @@ post "/api/check" do |ctx|
    ids = [] of Steam::ID
    raw_ids.each do |string_id|
      begin
        ids << Steam::ID.new(string_id)
        id = Steam::ID.new(string_id)
        # Enforce Public universe bit (STEAM_1..) and Individual account type:
        id.universe = :public
        id.account_type = :individual
        ids << id
      rescue ex : Steam::ID::Error
        job.send Job::Error.new(string_id, ex.message)
      end


@@ 62,7 66,10 @@ post "/api/check" do |ctx|

    ids.each do |id|
      unless player_ids.includes? id
        job.send Job::Error.new(id.to_steam_32, "Steam ID not found: #{id.to_steam_32}")
        job.send Job::Error.new(
          id.to_s(Steam::ID::Format::Default),
          "Steam ID not found: #{id.to_s(Steam::ID::Format::Default)}"
        )
      end
    end