steam_mist 1.3.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.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +29 -0
- data/lib/steam_mist/connector.rb +167 -0
- data/lib/steam_mist/connectors/eager_connector.rb +24 -0
- data/lib/steam_mist/connectors/lazy_connector.rb +33 -0
- data/lib/steam_mist/connectors.rb +11 -0
- data/lib/steam_mist/pseudo_interface/pseudo_method.rb +187 -0
- data/lib/steam_mist/pseudo_interface.rb +75 -0
- data/lib/steam_mist/rcon/listener.rb +114 -0
- data/lib/steam_mist/rcon/packet.rb +146 -0
- data/lib/steam_mist/rcon/packet_factory.rb +45 -0
- data/lib/steam_mist/rcon/pass.rb +123 -0
- data/lib/steam_mist/rcon.rb +47 -0
- data/lib/steam_mist/request_uri.rb +77 -0
- data/lib/steam_mist/schema.rb +42 -0
- data/lib/steam_mist/session.rb +60 -0
- data/lib/steam_mist/version.rb +5 -0
- data/lib/steam_mist.rb +7 -0
- data/spec/cache_spec.rb +25 -0
- data/spec/packet_factory_spec.rb +8 -0
- data/spec/packet_spec.rb +26 -0
- data/spec/pseudo_interface_spec.rb +15 -0
- data/spec/pseudo_method_spec.rb +37 -0
- data/spec/request_uri_spec.rb +19 -0
- data/spec/schema_spec.rb +17 -0
- data/spec/session_spec.rb +9 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9a5179398426a0232c5d3a8c68654c467594b1bb
|
4
|
+
data.tar.gz: bc17eacb58ff02a732c157d66b7ea8998b498184
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b751537ee70d925a7bb5a658810aabf6d1b2fd762eedc6e87b6e2d332df2c8d68d0999ac346ce6fb9f40e396cfc2448276b3ee1a42255a20abfb50d4bc3bd760
|
7
|
+
data.tar.gz: 77f86e721378b036199be3ce101d03ec0bdecd0c46d8c12da60084416dea406287994064bf170bb8bf69c70cd0020dc4d3434536ddfbe3b044a85af913cac0d5
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2013 Jeremy Rodi
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# SteamMist [](https://travis-ci.org/redjazz96/steam-mist) [](https://codeclimate.com/github/redjazz96/steam-mist)
|
2
|
+
Steam Mist (for the lack of a better name) is a library for interfacing with
|
3
|
+
the Steam Web API. It handles the HTTP requests for you while providing a
|
4
|
+
nice API for you to use.
|
5
|
+
|
6
|
+
Have some code samples:
|
7
|
+
|
8
|
+
```Ruby
|
9
|
+
require 'steam_mist'
|
10
|
+
session = SteamMist::Session.new SteamMist::Connectors::LazyConnector
|
11
|
+
session.default_arguments.merge! :key => "XXXXXXXXXXXXXXXXXXX", :format => :json
|
12
|
+
method = session.player_service.get_recently_played_games.with_version(1) \
|
13
|
+
.with_arguments(:steamid => "76561198025418738")
|
14
|
+
|
15
|
+
method.request_uri # =>
|
16
|
+
# http://api.steampowered.com/IPlayerService/GetRecentlyPlayedGames/v0001/
|
17
|
+
# ?key=XXXXXXXXXXXXXXXXX&steamid=76561197960434622&format=json
|
18
|
+
# It's a URI object so #inspect doesn't include the quotes ;)
|
19
|
+
method.get # => #<SteamMist::Connectors::LazyConnector>
|
20
|
+
method.get.data # => our json data
|
21
|
+
```
|
22
|
+
|
23
|
+
```Ruby
|
24
|
+
rcon = SteamMist::Rcon.new("localhost")
|
25
|
+
rcon.auth("password") # => true
|
26
|
+
rcon.send(:data => "echo hello") # =>
|
27
|
+
# [..., #<SteamMist::Rcon::Packet @body="hello\n", @type=0>, ...]
|
28
|
+
rcon.close
|
29
|
+
```
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'time'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module SteamMist
|
6
|
+
|
7
|
+
# @abstract Subclass and implement {#[], #each} to create a connector usable
|
8
|
+
# by SteamMist.
|
9
|
+
# @todo Test caching.
|
10
|
+
class Connector
|
11
|
+
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
# This is the data that the connector received from Steam. It may be null
|
15
|
+
# if the connector is lazy (in terms of when it grabs data).
|
16
|
+
#
|
17
|
+
# @return [Hash, nil]
|
18
|
+
attr_reader :data
|
19
|
+
|
20
|
+
# This is the request that the connector used to grab information from the
|
21
|
+
# steam api.
|
22
|
+
#
|
23
|
+
# @return [RequestUri]
|
24
|
+
attr_reader :request_uri
|
25
|
+
|
26
|
+
# Whether or not the connector has made the request to the API.
|
27
|
+
#
|
28
|
+
# @return [Boolean]
|
29
|
+
attr_reader :made_request
|
30
|
+
|
31
|
+
# This is a hash of headers that will be sent upon request.
|
32
|
+
#
|
33
|
+
# @return [Hash]
|
34
|
+
attr_reader :headers
|
35
|
+
|
36
|
+
# This initializes the connector.
|
37
|
+
#
|
38
|
+
# @param request_uri [RequestUri] the request uri to connect to.
|
39
|
+
def initialize(request_uri)
|
40
|
+
@request_uri = request_uri
|
41
|
+
@headers = {}
|
42
|
+
@cache = false
|
43
|
+
end
|
44
|
+
|
45
|
+
# Retrieve data from #data. This is normally used to force the connector
|
46
|
+
# (if it's lazy) to grab data.
|
47
|
+
#
|
48
|
+
# @param _ [Object] the key for data access.
|
49
|
+
# @raise NotImplementedError if the subclass hasn't implemented the method.
|
50
|
+
# @return [Object]
|
51
|
+
def [](_)
|
52
|
+
raise NotImplementedError
|
53
|
+
end
|
54
|
+
|
55
|
+
# This loops through the data. It should be implented for enumerable
|
56
|
+
# access.
|
57
|
+
#
|
58
|
+
# @raise NotImplementedError if the subclass hasn't implemented the method.
|
59
|
+
# @return [void]
|
60
|
+
def each
|
61
|
+
raise NotImplementedError
|
62
|
+
end
|
63
|
+
|
64
|
+
# If the connector is lazy, this should force the connector to make the
|
65
|
+
# request to Steam.
|
66
|
+
#
|
67
|
+
# @param force [Boolean] whether or not to force the request to
|
68
|
+
# return fresh data.
|
69
|
+
# @return [Hash] the data from the request to steam.
|
70
|
+
def force_request!(force = false)
|
71
|
+
@data = with_cache(force) { Oj.load(request, :mode => :strict) }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Enables caching on this connector.
|
75
|
+
#
|
76
|
+
# @param path [String] the path to the cache file.
|
77
|
+
# @return [Object]
|
78
|
+
def enable_caching(path)
|
79
|
+
@cache = path
|
80
|
+
end
|
81
|
+
|
82
|
+
# Whether or not this connector will cache the response.
|
83
|
+
#
|
84
|
+
# @return [Boolean]
|
85
|
+
def cache?
|
86
|
+
!!@cache
|
87
|
+
end
|
88
|
+
|
89
|
+
# Disables caching on this connector.
|
90
|
+
#
|
91
|
+
# @return [false]
|
92
|
+
def disable_caching
|
93
|
+
@cache = false
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
# The request used for retrieving information from Steam.
|
99
|
+
#
|
100
|
+
# @return [IO]
|
101
|
+
def request
|
102
|
+
@_request ||= open(request_uri, headers)
|
103
|
+
end
|
104
|
+
|
105
|
+
# This handles caching the file, if it was requested. Accepts a
|
106
|
+
# single block, which it yields to only when the cache data wasn't
|
107
|
+
# there.
|
108
|
+
#
|
109
|
+
# @return [Hash] the data.
|
110
|
+
def with_cache(force = false)
|
111
|
+
if cache
|
112
|
+
headers['If-Modified-Since'] = cache[:last_modified].utc.rfc2822
|
113
|
+
end
|
114
|
+
|
115
|
+
if force || !cache
|
116
|
+
write_cache yield
|
117
|
+
else
|
118
|
+
cache[:data]
|
119
|
+
end
|
120
|
+
|
121
|
+
rescue OpenURI::HTTPError => ex
|
122
|
+
if ex.message =~ /\A304 Not Modified\z/
|
123
|
+
return cache[:data]
|
124
|
+
else
|
125
|
+
raise ex
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Loads the data from the cache file.
|
130
|
+
#
|
131
|
+
# @param force [Boolean] whether or not to force loading from the
|
132
|
+
# file again.
|
133
|
+
def cache(force = false)
|
134
|
+
return nil unless cache? && File.exists?(@cache)
|
135
|
+
|
136
|
+
@_cache = if force || !@_cache
|
137
|
+
File.open(@cache) { |f| Oj.load(f) }[request_uri.to_s]
|
138
|
+
else
|
139
|
+
@_cache
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Writes the cache data to the file, and forces a reload of the
|
144
|
+
# cache data in memory.
|
145
|
+
#
|
146
|
+
# @param data [Object] the data to store.
|
147
|
+
# @return [Object] the data stored.
|
148
|
+
def write_cache(data)
|
149
|
+
return data unless cache?
|
150
|
+
write_data = {
|
151
|
+
request_uri.to_s => {
|
152
|
+
:data => data,
|
153
|
+
:last_modified => Time.now
|
154
|
+
}
|
155
|
+
}
|
156
|
+
|
157
|
+
FileUtils.mkdir_p File.dirname(@cache)
|
158
|
+
File.open(@cache, "w") do |f|
|
159
|
+
f.write Oj.dump(write_data, :time_format => :ruby)
|
160
|
+
end
|
161
|
+
|
162
|
+
cache(true)
|
163
|
+
data
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SteamMist
|
2
|
+
module Connectors
|
3
|
+
|
4
|
+
# An eager connector that forces the request on initialization.
|
5
|
+
class EagerConnector < Connector
|
6
|
+
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
# Calls `super` and then {#force_request!}.
|
10
|
+
def initialize(_)
|
11
|
+
super
|
12
|
+
|
13
|
+
@data = force_request!
|
14
|
+
end
|
15
|
+
|
16
|
+
# Implements the {#[]} method.
|
17
|
+
def_delegator :@data, :[]
|
18
|
+
|
19
|
+
# Implements the {#each} method.
|
20
|
+
def_delegator :@data, :each
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module SteamMist
|
2
|
+
module Connectors
|
3
|
+
|
4
|
+
# Lazily loads the data from Steam, which means the request is made only
|
5
|
+
# when or if the data is accessed.
|
6
|
+
class LazyConnector < Connector
|
7
|
+
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
# Provides access to the data.
|
11
|
+
#
|
12
|
+
# @return [Object]
|
13
|
+
def [](name)
|
14
|
+
data[name]
|
15
|
+
end
|
16
|
+
|
17
|
+
# This provides access to {#each} on the data.
|
18
|
+
#
|
19
|
+
# @see {Hash#each}.
|
20
|
+
def_delegator :data, :each
|
21
|
+
|
22
|
+
# This makes sure that the data was requested before it was used by
|
23
|
+
# checking if data is full (and if it isn't, fill it with the data from
|
24
|
+
# {#force_request!}).
|
25
|
+
#
|
26
|
+
# @return [Hash] the data.
|
27
|
+
def data
|
28
|
+
@data ||= force_request!
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module SteamMist
|
2
|
+
class PseudoInterface
|
3
|
+
|
4
|
+
# A representation of a Steam Web API method.
|
5
|
+
class PseudoMethod
|
6
|
+
|
7
|
+
# The version of the method. Used for {RequestUri}.
|
8
|
+
#
|
9
|
+
# @return [Numeric] the version.
|
10
|
+
attr_reader :version
|
11
|
+
|
12
|
+
# The name of the method.
|
13
|
+
#
|
14
|
+
# @return [Symbol] the name.
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
# The interface that this method is a part of.
|
18
|
+
#
|
19
|
+
# @return [PseudoInterface] the interface.
|
20
|
+
attr_reader :interface
|
21
|
+
|
22
|
+
# The arguments passed along with the method.
|
23
|
+
#
|
24
|
+
# @return [Hash] the arguments.
|
25
|
+
attr_reader :arguments
|
26
|
+
|
27
|
+
# Whether or not the connector is going to implement caching.
|
28
|
+
#
|
29
|
+
# @return [Boolean]
|
30
|
+
attr_reader :cached
|
31
|
+
|
32
|
+
# Initialize the method.
|
33
|
+
#
|
34
|
+
# @param interface [PseudoInterface] the interface this method is a part
|
35
|
+
# of. Used to help build the {RequestUri}.
|
36
|
+
# @param method [Symbol] the name of the method. See {#api_name} on how
|
37
|
+
# it's used.
|
38
|
+
# @param version [Numeric] the version of the method. Can be found on
|
39
|
+
# the steam web api wiki.
|
40
|
+
def initialize(interface, method, version=1)
|
41
|
+
@interface = interface
|
42
|
+
@name = method
|
43
|
+
@version = version
|
44
|
+
@arguments = {}
|
45
|
+
@cached = false
|
46
|
+
end
|
47
|
+
|
48
|
+
# This merges the passed hash with the arguments.
|
49
|
+
#
|
50
|
+
# @param new_arguments [Hash] the arguments to be added.
|
51
|
+
# @return [PseudoMethod] a {PseudoMethod} with the new arguments.
|
52
|
+
def with_arguments(new_arguments)
|
53
|
+
dup.with_arguments! new_arguments
|
54
|
+
end
|
55
|
+
|
56
|
+
# This merges the current arguments with the passed arguments. This
|
57
|
+
# modifies the instance it is called on. Invalidates the current
|
58
|
+
# connector.
|
59
|
+
#
|
60
|
+
# @param new_arguments [Hash] the arguments to merge with.
|
61
|
+
# @return [self]
|
62
|
+
def with_arguments!(new_arguments)
|
63
|
+
@arguments.merge!(new_arguments)
|
64
|
+
reset_connector
|
65
|
+
end
|
66
|
+
|
67
|
+
# This sets the version.
|
68
|
+
#
|
69
|
+
# @param new_version [Numeric] the version number to use.
|
70
|
+
# @return [PseudoMethod] a {PseudoMethod} with the version set to the
|
71
|
+
# new version.
|
72
|
+
def with_version(new_version)
|
73
|
+
dup.with_version!(new_version)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Sets the version of the current method. This modifies the instance it
|
77
|
+
# is called on. Invalidates the current connector.
|
78
|
+
#
|
79
|
+
# @param new_version [Numeric] the version to set to.
|
80
|
+
# @return [self]
|
81
|
+
def with_version!(new_version)
|
82
|
+
@version = new_version
|
83
|
+
reset_connector
|
84
|
+
end
|
85
|
+
|
86
|
+
# This makes sure the connector is set up to cache its results. Does not
|
87
|
+
# modify the connector until {#get} is called.
|
88
|
+
#
|
89
|
+
# @param path [String] the path of the cache file.
|
90
|
+
# @return [PseudoMethod] a {PseudoMethod} with caching.
|
91
|
+
def with_caching(path)
|
92
|
+
dup.with_caching!(path)
|
93
|
+
end
|
94
|
+
|
95
|
+
# This modifies the current method to make sure the connector is set up
|
96
|
+
# to cache its results. Invalidates the current connector.
|
97
|
+
#
|
98
|
+
# @param path [String] the path of the cache file.
|
99
|
+
# @return [self]
|
100
|
+
def with_caching!(path)
|
101
|
+
@cached = path
|
102
|
+
reset_connector
|
103
|
+
end
|
104
|
+
|
105
|
+
# This makes sure the connector is set up to not cache its results.
|
106
|
+
#
|
107
|
+
# @return [PseudoMethod] a {PseudoMethod} without caching.
|
108
|
+
def without_caching
|
109
|
+
dup.without_caching!
|
110
|
+
end
|
111
|
+
|
112
|
+
# This modifies the current method to make sure the connector is set up
|
113
|
+
# to not cache its results. Invalidates the current connector.
|
114
|
+
#
|
115
|
+
# @return [self]
|
116
|
+
def without_caching!
|
117
|
+
@cached = false
|
118
|
+
reset_connector
|
119
|
+
end
|
120
|
+
|
121
|
+
# Open up a connector for use. This is cached with this method.
|
122
|
+
#
|
123
|
+
# @return [Connector] the connector that will grab data.
|
124
|
+
def get
|
125
|
+
@_connector ||= begin
|
126
|
+
connector = interface.session.connector.new(request_uri)
|
127
|
+
|
128
|
+
if @cached
|
129
|
+
connector.enable_caching @cached
|
130
|
+
end
|
131
|
+
|
132
|
+
connector
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Turns the method name into the name the Steam Web API uses. It turns
|
137
|
+
# the method name from snake case to camel case if the first character
|
138
|
+
# isn't uppercase. Otherwise, it uses the string form of it.
|
139
|
+
#
|
140
|
+
# @return [String] the Steam Web API compatible method name.
|
141
|
+
def api_name
|
142
|
+
@_api_name ||= begin
|
143
|
+
str = name.to_s
|
144
|
+
|
145
|
+
if str[0] =~ /[A-Z]/
|
146
|
+
str
|
147
|
+
else
|
148
|
+
str.gsub!(/_[a-z]/) { |m| m[1].upcase }
|
149
|
+
str[0] = str[0].upcase
|
150
|
+
str
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# This turns the method into its corresponding {RequestUri}. It uses
|
156
|
+
# {#api_name} and {PseudoInterface#api_name} to help create the uri.
|
157
|
+
#
|
158
|
+
# @return [RequestUri]
|
159
|
+
def request_uri
|
160
|
+
@_request_uri ||= RequestUri.new :interface => interface.api_name,
|
161
|
+
:method => api_name,
|
162
|
+
:arguments => interface.session.default_arguments.dup.merge(arguments),
|
163
|
+
:version => version
|
164
|
+
end
|
165
|
+
|
166
|
+
# Pretty inspection.
|
167
|
+
#
|
168
|
+
# @return [String]
|
169
|
+
def inspect
|
170
|
+
"#<SteamMist::PseudoInterface::PseudoMethod #{interface.name}/#{name}>"
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
# This is used to show that the connector is out of date; it forces the
|
176
|
+
# method to use a seperate connector.
|
177
|
+
#
|
178
|
+
# @return [self]
|
179
|
+
def reset_connector
|
180
|
+
@_connector = nil
|
181
|
+
@_request_uri = nil
|
182
|
+
self
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'steam_mist/pseudo_interface/pseudo_method'
|
2
|
+
|
3
|
+
module SteamMist
|
4
|
+
# This basically represents an interface for the Web API.
|
5
|
+
class PseudoInterface
|
6
|
+
|
7
|
+
# The interface's name that this is representing. This should be in
|
8
|
+
# underscore form.
|
9
|
+
#
|
10
|
+
# @return [Symbol] the name of the interface.
|
11
|
+
attr_reader :name
|
12
|
+
|
13
|
+
# The session that the interface is running under. This is used mainly for
|
14
|
+
# the connector.
|
15
|
+
#
|
16
|
+
# @return [Session]
|
17
|
+
attr_reader :session
|
18
|
+
|
19
|
+
# Initialize the pseudointerface with the interface name. See {#api_name}
|
20
|
+
# for information about how this is handled.
|
21
|
+
#
|
22
|
+
# @param session [Session] the session the interface is a part of.
|
23
|
+
# @param interface_name [Symbol] the interface name.
|
24
|
+
def initialize(session, interface_name)
|
25
|
+
@name = interface_name
|
26
|
+
@session = session
|
27
|
+
end
|
28
|
+
|
29
|
+
# Grab a method from this interface.
|
30
|
+
#
|
31
|
+
# @param method_name [Symbol] the name of the method.
|
32
|
+
# @param version [Numeric] the version of the method.
|
33
|
+
# @return [PseudoMethod]
|
34
|
+
def get_method(method_name, version=1)
|
35
|
+
PseudoMethod.new(self, method_name, version)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Turns the interface name into the corresponding api name. If the
|
39
|
+
# interface starts with an `I`, it makes no modifications to it (other than
|
40
|
+
# turning it into a string). If it doesn't, however, it turns it from
|
41
|
+
# snake case to camel case (i.e. `some_interface` to `SomeInterface`).
|
42
|
+
# It then adds an `I` to the front of it.
|
43
|
+
#
|
44
|
+
# @return [String] the API name used by the Steam Web API.
|
45
|
+
def api_name
|
46
|
+
@_api_name ||= begin
|
47
|
+
str = name.to_s
|
48
|
+
|
49
|
+
if str[0] == "I"
|
50
|
+
str
|
51
|
+
else
|
52
|
+
str.gsub!(/_([a-z])/) { |m| m[1].upcase }
|
53
|
+
str[0] = str[0].upcase
|
54
|
+
"I#{str}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Some {#method_missing} magic. Sorry @charliesome!
|
60
|
+
#
|
61
|
+
# @see {#get_method}
|
62
|
+
def method_missing(method, *args)
|
63
|
+
super if args.length > 1 or block_given?
|
64
|
+
get_method method, *args
|
65
|
+
end
|
66
|
+
|
67
|
+
# Pretty inspection.
|
68
|
+
#
|
69
|
+
# @return [String]
|
70
|
+
def inspect
|
71
|
+
"#<SteamMist::PseudoInterface #{name}>"
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module SteamMist
|
2
|
+
class Rcon
|
3
|
+
|
4
|
+
# Listens on a TCP stream for packets. This is completely synchronus.
|
5
|
+
class Listener
|
6
|
+
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
# The ip address this listener is bound to.
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
attr_reader :ip
|
13
|
+
|
14
|
+
# The port the listener is bound to.
|
15
|
+
#
|
16
|
+
# @return [Numeric]
|
17
|
+
attr_reader :port
|
18
|
+
|
19
|
+
# The connection the listener is using.
|
20
|
+
#
|
21
|
+
# @return [TCPSocket]
|
22
|
+
attr_reader :connection
|
23
|
+
|
24
|
+
# Initialize the listener.
|
25
|
+
#
|
26
|
+
# @param ip [String] the ip address to bind to.
|
27
|
+
# @param port [Numeric] the port to bind to.
|
28
|
+
def initialize(ip, port)
|
29
|
+
@ip = ip
|
30
|
+
@port = port
|
31
|
+
@closed = false
|
32
|
+
end
|
33
|
+
|
34
|
+
# Connect to the set port and ip address.
|
35
|
+
#
|
36
|
+
# @return [void]
|
37
|
+
def bind!
|
38
|
+
@connection = TCPSocket.new ip, port
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sets the ip.
|
42
|
+
#
|
43
|
+
# @param new_ip [String]
|
44
|
+
# @raise [ArgumentError] if already connected.
|
45
|
+
# @return [void]
|
46
|
+
def ip=(new_ip)
|
47
|
+
raise ArgumentError,
|
48
|
+
"Already connected; cannot change IP" if connection
|
49
|
+
|
50
|
+
@ip = new_ip
|
51
|
+
end
|
52
|
+
|
53
|
+
# Sets the port.
|
54
|
+
#
|
55
|
+
# @param new_port [Numeric]
|
56
|
+
# @raise [ArgumentError] if already connected.
|
57
|
+
# @return [void]
|
58
|
+
def port=(new_port)
|
59
|
+
raise ArgumentError,
|
60
|
+
"Already connected; cannot change port" if connection
|
61
|
+
|
62
|
+
@port = new_port
|
63
|
+
end
|
64
|
+
|
65
|
+
# Listens to the connection for any data; when there is some, it'll call
|
66
|
+
# the block and then return.
|
67
|
+
#
|
68
|
+
# @yieldparam socket [TCPSocket]
|
69
|
+
# @yieldreturn [void]
|
70
|
+
# @raise [TimeoutError] when select takes too long.
|
71
|
+
# @return [void]
|
72
|
+
def on_data
|
73
|
+
return false if closed?
|
74
|
+
|
75
|
+
result = IO.select [connection], [], [], 10
|
76
|
+
|
77
|
+
raise TimeoutError, "timeout" unless result
|
78
|
+
|
79
|
+
yield connection
|
80
|
+
end
|
81
|
+
|
82
|
+
# Closes the connection.
|
83
|
+
#
|
84
|
+
# @return [void]
|
85
|
+
def close
|
86
|
+
unless @closed
|
87
|
+
@closed = true
|
88
|
+
connection.close
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# This returns true or false depending on whether or not the connection
|
93
|
+
# is closed.
|
94
|
+
#
|
95
|
+
# @return [Boolean]
|
96
|
+
def closed?
|
97
|
+
@closed
|
98
|
+
end
|
99
|
+
|
100
|
+
# This passes the write through to the connection if the connection isn't
|
101
|
+
# closed.
|
102
|
+
def write(*args, &block)
|
103
|
+
return false if closed?
|
104
|
+
|
105
|
+
puts "writing #{args[0]}"
|
106
|
+
|
107
|
+
connection.write(*args, &block)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# When {IO#select} takes too long to respond, this is raised.
|
112
|
+
class TimeoutError < IOError; end
|
113
|
+
end
|
114
|
+
end
|