steam_mist 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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