steam_mist 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/redjazz96/steam-mist.png?branch=master)](https://travis-ci.org/redjazz96/steam-mist) [![Code Climate](https://codeclimate.com/github/redjazz96/steam-mist.png)](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
|