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 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,11 @@
1
+ module SteamMist
2
+
3
+ # The connectors that will be used by SteamMist.
4
+ module Connectors
5
+
6
+ autoload :LazyConnector, "steam_mist/connectors/lazy_connector"
7
+
8
+ autoload :EagerConnector, "steam_mist/connectors/eager_connector"
9
+
10
+ end
11
+ 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