steam-condenser 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +10 -7
- data/VERSION.yml +4 -0
- data/lib/byte_buffer.rb +28 -28
- data/lib/datagram_channel.rb +5 -4
- data/lib/exceptions/rcon_ban_exception.rb +12 -0
- data/lib/socket_channel.rb +21 -14
- data/lib/steam-condenser.rb +11 -0
- data/lib/steam/community/cacheable.rb +95 -0
- data/lib/steam/community/defense_grid/defense_grid_stats.rb +124 -0
- data/lib/steam/community/game_stats.rb +44 -26
- data/lib/steam/community/l4d/l4d_map.rb +3 -3
- data/lib/steam/community/steam_group.rb +77 -6
- data/lib/steam/community/steam_id.rb +127 -101
- data/lib/steam/community/tf2/tf2_stats.rb +5 -5
- data/lib/steam/packets/rcon/rcon_exec_response.rb +6 -8
- data/lib/steam/packets/s2a_rules_packet.rb +15 -8
- data/lib/steam/servers/game_server.rb +2 -4
- data/lib/steam/servers/goldsrc_server.rb +5 -5
- data/lib/steam/servers/source_server.rb +11 -11
- data/lib/steam/sockets/rcon_socket.rb +18 -17
- data/lib/steam/sockets/steam_socket.rb +5 -8
- data/lib/steam/steam_player.rb +5 -5
- data/test/byte_buffer_tests.rb +76 -0
- data/test/datagram_channel_tests.rb +42 -0
- data/test/query_tests.rb +1 -3
- data/test/rcon_tests.rb +1 -3
- data/test/socket_channel_tests.rb +43 -0
- data/test/steam/communtiy/steam_community_test_suite.rb +9 -0
- data/test/steam/communtiy/steam_group_tests.rb +43 -0
- data/test/steam/communtiy/steam_id_tests.rb +48 -0
- data/test/steam_community_tests.rb +17 -9
- metadata +27 -10
@@ -12,12 +12,12 @@ class TF2Stats < GameStats
|
|
12
12
|
attr_reader :accumulated_points
|
13
13
|
|
14
14
|
# Creates a TF2Stats object by calling the super constructor with the game
|
15
|
-
# name "
|
15
|
+
# name "tf2"
|
16
16
|
def initialize(steam_id)
|
17
|
-
super steam_id,
|
17
|
+
super steam_id, 'tf2'
|
18
18
|
|
19
19
|
if public?
|
20
|
-
@accumulated_points = @xml_data.elements[
|
20
|
+
@accumulated_points = @xml_data.elements['stats/accumulatedPoints'].text.to_i
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -28,8 +28,8 @@ class TF2Stats < GameStats
|
|
28
28
|
|
29
29
|
if @class_stats.nil?
|
30
30
|
@class_stats = Hash.new
|
31
|
-
@xml_data.elements
|
32
|
-
@class_stats[class_data.elements[
|
31
|
+
@xml_data.elements.each('stats/classData') do |class_data|
|
32
|
+
@class_stats[class_data.elements['className'].text] = TF2Class.new(class_data)
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -1,20 +1,18 @@
|
|
1
1
|
# This code is free software; you can redistribute it and/or modify it under the
|
2
2
|
# terms of the new BSD License.
|
3
3
|
#
|
4
|
-
# Copyright (c) 2008, Sebastian Staudt
|
5
|
-
#
|
6
|
-
# $Id$
|
4
|
+
# Copyright (c) 2008-2009, Sebastian Staudt
|
7
5
|
|
8
6
|
require "steam/packets/rcon/rcon_packet"
|
9
7
|
|
10
8
|
class RCONExecResponse < RCONPacket
|
11
|
-
|
9
|
+
|
12
10
|
def initialize(request_id, command_response)
|
13
11
|
super request_id, RCONPacket::SERVERDATA_RESPONSE_VALUE, command_response
|
14
12
|
end
|
15
|
-
|
13
|
+
|
16
14
|
def get_response
|
17
|
-
|
15
|
+
@content_data.array[0..-3]
|
18
16
|
end
|
19
|
-
|
20
|
-
end
|
17
|
+
|
18
|
+
end
|
@@ -2,27 +2,34 @@
|
|
2
2
|
# terms of the new BSD License.
|
3
3
|
#
|
4
4
|
# Copyright (c) 2008, Sebastian Staudt
|
5
|
-
#
|
6
|
-
# $Id$
|
7
5
|
|
8
6
|
require "steam/packets/steam_packet"
|
9
7
|
|
10
8
|
# The S2A_RULES_Packet class represents the response to a A2S_RULES
|
11
9
|
# request send to the server.
|
12
10
|
class S2A_RULES_Packet < SteamPacket
|
13
|
-
|
11
|
+
|
14
12
|
# Creates a S2A_RULES response object based on the data received.
|
15
13
|
def initialize(content_data)
|
16
14
|
if content_data == nil
|
17
15
|
raise Exception.new("Wrong formatted A2A_RULES response packet.")
|
18
16
|
end
|
19
|
-
|
17
|
+
|
20
18
|
super SteamPacket::S2A_RULES_HEADER, content_data
|
21
|
-
|
22
|
-
@rules_hash = Hash.new @content_data.get_short
|
23
19
|
|
24
|
-
|
25
|
-
|
20
|
+
rules_count = @content_data.get_short
|
21
|
+
|
22
|
+
@rules_hash = {}
|
23
|
+
|
24
|
+
rules_count.times do
|
25
|
+
rule = @content_data.get_string
|
26
|
+
value = @content_data.get_string
|
27
|
+
|
28
|
+
# This is a workaround for servers sending corrupt replies
|
29
|
+
break if rule.empty? or value.empty?
|
30
|
+
|
31
|
+
@rules_hash[rule] = value
|
32
|
+
puts "#{rule} = #{value}"
|
26
33
|
end
|
27
34
|
end
|
28
35
|
|
@@ -142,13 +142,11 @@ class GameServer
|
|
142
142
|
unless rcon_password.nil? or @player_hash.empty?
|
143
143
|
rcon_auth(rcon_password)
|
144
144
|
players = rcon_exec('status').split("\n")[7..-1]
|
145
|
-
if is_a? GoldSrcServer
|
146
|
-
players.pop
|
147
|
-
end
|
145
|
+
players.pop if is_a? GoldSrcServer
|
148
146
|
|
149
147
|
players.each do |player|
|
150
148
|
player_data = split_player_status(player)
|
151
|
-
@player_hash[player_data[1]].add_info(*player_data)
|
149
|
+
@player_hash[player_data[1]].add_info(*player_data) if @player_hash.key?(player_data[1])
|
152
150
|
end
|
153
151
|
end
|
154
152
|
end
|
@@ -10,7 +10,7 @@ class GoldSrcServer < GameServer
|
|
10
10
|
|
11
11
|
# Splits the player status obtained with +rcon status+
|
12
12
|
def self.split_player_status(player_status)
|
13
|
-
player_data = player_status.match(/#(
|
13
|
+
player_data = player_status.match(/# *(\d+) +"(.*)" +(\d+) +(.*)/).to_a[1..-1]
|
14
14
|
player_data[3] = player_data[3].split
|
15
15
|
player_data.flatten!
|
16
16
|
player_data[0] = player_data[2]
|
@@ -23,14 +23,14 @@ class GoldSrcServer < GameServer
|
|
23
23
|
super port_number
|
24
24
|
@socket = GoldSrcSocket.new ip_address, port_number, is_hltv
|
25
25
|
end
|
26
|
-
|
26
|
+
|
27
27
|
def rcon_auth(password)
|
28
28
|
@rcon_password = password
|
29
29
|
return true
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
def rcon_exec(command)
|
33
33
|
return @socket.rcon_exec(@rcon_password, command).strip
|
34
34
|
end
|
35
|
-
|
36
|
-
end
|
35
|
+
|
36
|
+
end
|
@@ -15,35 +15,35 @@ class SourceServer < GameServer
|
|
15
15
|
|
16
16
|
# Splits the player status obtained with +rcon status+
|
17
17
|
def self.split_player_status(player_status)
|
18
|
-
player_data = player_status.match(/#(
|
18
|
+
player_data = player_status.match(/# *(\d+) +"(.*)" +(.*)/).to_a[1..-1]
|
19
19
|
player_data[2] = player_data[2].split
|
20
20
|
player_data.flatten
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
def initialize(ip_address, port_number = 27015)
|
24
24
|
super port_number
|
25
25
|
@rcon_socket = RCONSocket.new ip_address, port_number
|
26
26
|
@socket = SourceSocket.new ip_address, port_number
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
def rcon_auth(password)
|
30
30
|
@rcon_request_id = rand 2**16
|
31
|
-
|
31
|
+
|
32
32
|
@rcon_socket.send RCONAuthRequest.new(@rcon_request_id, password)
|
33
33
|
@rcon_socket.get_reply
|
34
34
|
reply = @rcon_socket.get_reply
|
35
|
-
|
35
|
+
|
36
36
|
if reply.request_id == -1
|
37
37
|
raise RCONNoAuthException.new
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
return reply.get_request_id == @rcon_request_id
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
def rcon_exec(command)
|
44
44
|
@rcon_socket.send RCONExecRequest.new(@rcon_request_id, command)
|
45
45
|
response_packets = Array.new
|
46
|
-
|
46
|
+
|
47
47
|
begin
|
48
48
|
begin
|
49
49
|
response_packet = @rcon_socket.get_reply
|
@@ -53,13 +53,13 @@ class SourceServer < GameServer
|
|
53
53
|
rescue TimeoutException
|
54
54
|
raise $! if response_packets.empty?
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
response = String.new
|
58
58
|
response_packets.each do |packet|
|
59
59
|
response << packet.get_response
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
return response.strip
|
63
63
|
end
|
64
64
|
|
65
|
-
end
|
65
|
+
end
|
@@ -1,38 +1,39 @@
|
|
1
1
|
# This code is free software; you can redistribute it and/or modify it under the
|
2
2
|
# terms of the new BSD License.
|
3
3
|
#
|
4
|
-
# Copyright (c) 2008, Sebastian Staudt
|
5
|
-
#
|
6
|
-
# $Id$
|
4
|
+
# Copyright (c) 2008-2009, Sebastian Staudt
|
7
5
|
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
6
|
+
require 'socket_channel'
|
7
|
+
require 'exceptions/rcon_ban_exception'
|
8
|
+
require 'steam/packets/rcon/rcon_packet'
|
9
|
+
require 'steam/packets/rcon/rcon_packet_factory'
|
10
|
+
require 'steam/sockets/steam_socket'
|
12
11
|
|
13
12
|
class RCONSocket < SteamSocket
|
14
|
-
|
13
|
+
|
15
14
|
def initialize(ip_address, port_number)
|
16
15
|
super ip_address, port_number
|
17
16
|
@channel = SocketChannel.open
|
18
17
|
end
|
19
|
-
|
18
|
+
|
20
19
|
def send(data_packet)
|
21
20
|
if !@channel.connected?
|
22
21
|
@channel.connect @remote_socket
|
23
22
|
end
|
24
|
-
|
23
|
+
|
25
24
|
@buffer = ByteBuffer.wrap data_packet.get_bytes
|
26
25
|
@channel.write @buffer
|
27
26
|
end
|
28
|
-
|
27
|
+
|
29
28
|
def get_reply
|
30
|
-
self.receive_packet
|
29
|
+
if self.receive_packet(1440) == 0
|
30
|
+
raise RCONBanException
|
31
|
+
end
|
31
32
|
packet_data = @buffer.array[0..@buffer.limit]
|
32
33
|
packet_size = @buffer.get_long + 4
|
33
|
-
|
34
|
+
|
34
35
|
if packet_size > 1440
|
35
|
-
remaining_bytes = packet_size -
|
36
|
+
remaining_bytes = packet_size - @buffer.limit
|
36
37
|
begin
|
37
38
|
if remaining_bytes < 1440
|
38
39
|
self.receive_packet remaining_bytes
|
@@ -43,8 +44,8 @@ class RCONSocket < SteamSocket
|
|
43
44
|
remaining_bytes -= @buffer.limit
|
44
45
|
end while remaining_bytes > 0
|
45
46
|
end
|
46
|
-
|
47
|
+
|
47
48
|
return RCONPacketFactory.get_packet_from_data(packet_data)
|
48
49
|
end
|
49
|
-
|
50
|
-
end
|
50
|
+
|
51
|
+
end
|
@@ -1,9 +1,7 @@
|
|
1
1
|
# This code is free software; you can redistribute it and/or modify it under the
|
2
2
|
# terms of the new BSD License.
|
3
3
|
#
|
4
|
-
# Copyright (c) 2008, Sebastian Staudt
|
5
|
-
#
|
6
|
-
# $Id$
|
4
|
+
# Copyright (c) 2008-2009, Sebastian Staudt
|
7
5
|
|
8
6
|
require "abstract_class"
|
9
7
|
require "byte_buffer"
|
@@ -41,19 +39,18 @@ class SteamSocket
|
|
41
39
|
@buffer = ByteBuffer.allocate buffer_length
|
42
40
|
end
|
43
41
|
|
44
|
-
@channel.read
|
45
|
-
bytes_read = @buffer.position
|
42
|
+
bytes_read = @channel.read(@buffer)
|
46
43
|
@buffer.rewind
|
47
44
|
@buffer.limit = bytes_read
|
48
45
|
|
49
|
-
|
46
|
+
bytes_read
|
50
47
|
end
|
51
48
|
|
52
49
|
def send(data_packet)
|
53
50
|
warn "Sending data packet of type \"#{data_packet.class.to_s}\"."
|
54
51
|
|
55
|
-
@buffer = ByteBuffer.wrap
|
56
|
-
@channel.write
|
52
|
+
@buffer = ByteBuffer.wrap(data_packet.to_s)
|
53
|
+
@channel.write(@buffer)
|
57
54
|
end
|
58
55
|
|
59
56
|
def finalize
|
data/lib/steam/steam_player.rb
CHANGED
@@ -8,9 +8,9 @@ require 'exceptions/steam_condenser_exception'
|
|
8
8
|
# The SteamPlayer class represents a player connected to a server
|
9
9
|
class SteamPlayer
|
10
10
|
|
11
|
-
attr_reader :
|
12
|
-
:steam_id
|
13
|
-
|
11
|
+
attr_reader :client_port, :connect_time, :id, :ip_address, :name, :loss,
|
12
|
+
:ping, :real_id, :score, :state, :steam_id
|
13
|
+
|
14
14
|
# Creates a new SteamPlayer object based on the given information
|
15
15
|
def initialize(id, name, score, connect_time)
|
16
16
|
@connect_time = connect_time
|
@@ -32,7 +32,7 @@ class SteamPlayer
|
|
32
32
|
if steam_id == 'BOT'
|
33
33
|
@state = player_data[0]
|
34
34
|
else
|
35
|
-
@
|
35
|
+
@ip_address, @client_port = player_data[4].split(':')
|
36
36
|
@loss = player_data[2]
|
37
37
|
@ping = player_data[1]
|
38
38
|
@state = player_data[3]
|
@@ -47,5 +47,5 @@ class SteamPlayer
|
|
47
47
|
"\##{@id} \"#{@name}\", Score: #{@score}, Time: #{@connect_time}"
|
48
48
|
end
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
|
7
|
+
|
8
|
+
require 'test/unit'
|
9
|
+
require 'byte_buffer'
|
10
|
+
|
11
|
+
class ByteBufferTests < Test::Unit::TestCase
|
12
|
+
|
13
|
+
def test_allocate
|
14
|
+
buffer = ByteBuffer.allocate(10)
|
15
|
+
assert_equal("\0" * 10, buffer.array)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_get_byte
|
19
|
+
buffer = ByteBuffer.wrap('test')
|
20
|
+
assert_equal('t'[0], buffer.get_byte)
|
21
|
+
assert_equal(3, buffer.remaining)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_get_float
|
25
|
+
buffer = ByteBuffer.wrap('test')
|
26
|
+
assert_equal('7.71353668494131e+31', buffer.get_float.to_s)
|
27
|
+
assert_equal(0, buffer.remaining)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_get_long
|
31
|
+
buffer = ByteBuffer.wrap('test')
|
32
|
+
assert_equal(1953719668, buffer.get_long)
|
33
|
+
assert_equal(0, buffer.remaining)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_get_short
|
37
|
+
buffer = ByteBuffer.wrap('test')
|
38
|
+
assert_equal(25972, buffer.get_short)
|
39
|
+
assert_equal(2, buffer.remaining)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_get_signed_long
|
43
|
+
buffer = ByteBuffer.wrap(" \255")
|
44
|
+
assert_equal(-1390403552, buffer.get_signed_long)
|
45
|
+
assert_equal(0, buffer.remaining)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_get_string
|
49
|
+
buffer = ByteBuffer.wrap("test\0test")
|
50
|
+
assert_equal('test', buffer.get_string)
|
51
|
+
assert_equal(4, buffer.remaining)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_put
|
55
|
+
buffer = ByteBuffer.wrap('te')
|
56
|
+
buffer.put('st')
|
57
|
+
assert_equal('st', buffer.array)
|
58
|
+
buffer = ByteBuffer.allocate(4)
|
59
|
+
buffer.put('test')
|
60
|
+
assert_equal('test', buffer.array)
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_rewind
|
64
|
+
buffer = ByteBuffer.wrap('test')
|
65
|
+
assert_equal(25972, buffer.get_short)
|
66
|
+
buffer.rewind
|
67
|
+
assert_equal(25972, buffer.get_short)
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_wrap
|
71
|
+
string = 'test'
|
72
|
+
buffer = ByteBuffer.wrap(string)
|
73
|
+
assert_equal(string, buffer.array)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# This code is free software; you can redistribute it and/or modify it under the
|
2
|
+
# terms of the new BSD License.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2009, Sebastian Staudt
|
5
|
+
|
6
|
+
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
|
7
|
+
|
8
|
+
require 'test/unit'
|
9
|
+
|
10
|
+
require 'byte_buffer'
|
11
|
+
require 'datagram_channel'
|
12
|
+
|
13
|
+
class DatagramChannelTests < Test::Unit::TestCase
|
14
|
+
|
15
|
+
def test_read_write
|
16
|
+
port = rand(65536 - 1024) + 1024
|
17
|
+
|
18
|
+
socket = UDPSocket.new
|
19
|
+
socket.bind('localhost', port)
|
20
|
+
|
21
|
+
channel = DatagramChannel.open
|
22
|
+
channel.connect('localhost', port)
|
23
|
+
|
24
|
+
string = 'test'
|
25
|
+
|
26
|
+
buffer = ByteBuffer.wrap(string)
|
27
|
+
channel.write(buffer)
|
28
|
+
sent, socket_addr = socket.recvfrom(4)
|
29
|
+
socket.send(string, 0, 'localhost', socket_addr[1])
|
30
|
+
buffer = ByteBuffer.allocate(4)
|
31
|
+
channel.read(buffer)
|
32
|
+
|
33
|
+
channel.close
|
34
|
+
socket.close
|
35
|
+
|
36
|
+
received = buffer.array
|
37
|
+
|
38
|
+
assert_equal(string, sent)
|
39
|
+
assert_equal(string, received)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|