stew 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/Guardfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +97 -0
  7. data/Rakefile +9 -0
  8. data/lib/stew.rb +58 -0
  9. data/lib/stew/community/profile.rb +23 -0
  10. data/lib/stew/community/profile_friends.rb +18 -0
  11. data/lib/stew/community/profile_game.rb +28 -0
  12. data/lib/stew/community/profile_games.rb +17 -0
  13. data/lib/stew/community/steam_id.rb +52 -0
  14. data/lib/stew/community_client.rb +42 -0
  15. data/lib/stew/store/app.rb +76 -0
  16. data/lib/stew/store/app_offer.rb +38 -0
  17. data/lib/stew/store/app_offer_sale.rb +23 -0
  18. data/lib/stew/store/app_offers.rb +25 -0
  19. data/lib/stew/store_client.rb +39 -0
  20. data/lib/stew/version.rb +3 -0
  21. data/lib/stew/web_client.rb +30 -0
  22. data/lib/stew/xml_client.rb +49 -0
  23. data/spec/fixtures/profiles/4d.txt +16 -0
  24. data/spec/fixtures/profiles/76561197992917668.txt +122 -0
  25. data/spec/fixtures/profiles/76561197992917668.yml +94 -0
  26. data/spec/fixtures/profiles/friends/76561197992917668.yml +36 -0
  27. data/spec/fixtures/profiles/games/76561197992917668.yml +616 -0
  28. data/spec/fixtures/store/apps/16870.txt +1078 -0
  29. data/spec/fixtures/store/apps/211400_offers_sale.txt +46 -0
  30. data/spec/fixtures/store/apps/211400_sale.txt +1327 -0
  31. data/spec/fixtures/store/apps/211420.txt +1320 -0
  32. data/spec/fixtures/store/apps/211420_us.txt +1306 -0
  33. data/spec/fixtures/store/apps/219150.txt +1134 -0
  34. data/spec/fixtures/store/apps/2290.txt +1059 -0
  35. data/spec/fixtures/store/apps/49520.txt +1471 -0
  36. data/spec/fixtures/store/apps/49520_offers.txt +111 -0
  37. data/spec/fixtures/store/apps/no_app.txt +5 -0
  38. data/spec/integration/community_integration_spec.rb +50 -0
  39. data/spec/integration/store_integration_spec.rb +129 -0
  40. data/spec/lib/stew/community/profile_friends_spec.rb +38 -0
  41. data/spec/lib/stew/community/profile_game_spec.rb +37 -0
  42. data/spec/lib/stew/community/profile_games_spec.rb +36 -0
  43. data/spec/lib/stew/community/profile_spec.rb +18 -0
  44. data/spec/lib/stew/community/steam_id_spec.rb +116 -0
  45. data/spec/lib/stew/community_client_spec.rb +88 -0
  46. data/spec/lib/stew/store/app_offer_sale_spec.rb +36 -0
  47. data/spec/lib/stew/store/app_offer_spec.rb +47 -0
  48. data/spec/lib/stew/store/app_offers_spec.rb +64 -0
  49. data/spec/lib/stew/store/app_spec.rb +142 -0
  50. data/spec/lib/stew/store_client_spec.rb +58 -0
  51. data/spec/lib/stew/web_client_spec.rb +25 -0
  52. data/spec/lib/stew/xml_client_spec.rb +36 -0
  53. data/spec/lib/stew_spec.rb +4 -0
  54. data/spec/spec_helper.rb +23 -0
  55. data/stew.gemspec +38 -0
  56. metadata +412 -0
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ spec/fixtures/cassette_library/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in stew.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ guard 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
6
+
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Magnus Skog
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # Stew
2
+
3
+ [![Code Climate](https://codeclimate.com/github/mskog/stew.png)](https://codeclimate.com/github/mskog/stew)
4
+
5
+ ##Information
6
+
7
+ Stew can access both the Steam Community and the Steam Store. The Community library uses the Steam XML API and should be rather stable.
8
+ The Store library accesses the Store pages by parsing the HTML. This should be considered unstable and you have to be prepared for missing data in case the store pages change.
9
+
10
+ ###Steam Community
11
+ The Community part of the API is a work in progress and lacks a lot of the fields expected in for example a Profile.
12
+ You can however still use it to retrieve the games for a profile and such.
13
+
14
+ ####Create a Steam ID
15
+ ```ruby
16
+ steam_id = Stew::Community::SteamId.create("http://steamcommunity.com/profiles/76561197992917668")
17
+ ```
18
+
19
+ You can also use the 64 bit ids. This is equivalent to above:
20
+ ```ruby
21
+ steam_id = Stew::Community::SteamId.create(76561197992917668)
22
+ ```
23
+
24
+ Finally, you can use vanity names or URLs to create steam_ids like so:
25
+
26
+ ```ruby
27
+ steam_id = Stew::Community::SteamId.create("http://steamcommunity.com/id/eekon20")
28
+ ```
29
+ or
30
+ ```ruby
31
+ steam_id = Stew::Community::SteamId.create("eekon20")
32
+ ```
33
+
34
+ Once you have created a Steam ID, you can use it to get information like the Profile, Games and Friends
35
+
36
+ ##### Profile
37
+ ```ruby
38
+ steam_id.profile.nickname #=> 'Best baunty EU'
39
+ ```
40
+
41
+ ##### Games
42
+ ```ruby
43
+ steam_id.games.each {|game| puts game.name}
44
+ ```
45
+
46
+ ##### Friends
47
+ ```ruby
48
+ steam_id.friends.each {|friend| puts friend.profile.nickname}
49
+ ```
50
+
51
+
52
+ ## Store
53
+ **Important! The Store library accesses the Store pages by parsing HTML responses. It will give nil values in case the HTML on the store pages change**
54
+
55
+ #### Create a Store Application
56
+
57
+ ##### From an App id
58
+ ```ruby
59
+ app = Stew::StoreClient.new.create_app(220240)
60
+ ```
61
+
62
+ ##### From a URL
63
+ ```ruby
64
+ app = Stew::StoreClient.new.create_app("http://store.steampowered.com/app/220240/")
65
+ ```
66
+
67
+ ##### From a URL with a region
68
+ ```ruby
69
+ app = Stew::StoreClient.new.create_app("http://store.steampowered.com/app/220240/?cc=uk")
70
+ ```
71
+
72
+ All the examples above will create a Stew::Store::App instance for the game Far Cry 3. You can then access data like so:
73
+
74
+ ```ruby
75
+ app.name #=> "Far Cry 3"
76
+
77
+ app.score #=> 88 #Metacritic score
78
+
79
+ app.release_date.to_s #=> "2012-12-04"
80
+
81
+ app.price #=> Money instance. https://github.com/RubyMoney/money
82
+
83
+ app.offers #=> AppOffers
84
+
85
+ app.offers.sale? #=> false
86
+
87
+ app.indie? #=> false
88
+
89
+ app.free? #=> false :(
90
+ ```
91
+
92
+ More data is available for apps and app offers. See the code for the specific classes to find out.
93
+
94
+ ## Contribute
95
+
96
+ Will gladly accept pull requests.
97
+
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake'
2
+ require "bundler/gem_tasks"
3
+ require 'ci/reporter/rake/rspec'
4
+ require 'rspec/core/rake_task'
5
+
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task :default => :spec
data/lib/stew.rb ADDED
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ require 'faraday'
4
+ require 'faraday_middleware'
5
+ require 'yaml'
6
+ require 'nokogiri'
7
+ require 'money'
8
+
9
+ require "stew/version"
10
+
11
+ require 'stew/web_client'
12
+ require 'stew/xml_client'
13
+ require 'stew/community_client'
14
+ require 'stew/store_client'
15
+
16
+ require 'stew/community/steam_id'
17
+ require 'stew/community/profile'
18
+ require 'stew/community/profile_friends'
19
+ require 'stew/community/profile_game'
20
+ require 'stew/community/profile_games'
21
+
22
+ require 'stew/store/app'
23
+ require 'stew/store/app_offers'
24
+ require 'stew/store/app_offer'
25
+ require 'stew/store/app_offer_sale'
26
+
27
+ module Stew
28
+ Money.assume_from_symbol = true
29
+
30
+ @config = {
31
+ :default_community_client => CommunityClient,
32
+ :default_store_client => StoreClient,
33
+ :default_xml_client => XmlClient,
34
+ :default_web_client => WebClient,
35
+ :default_region => :us
36
+ }
37
+
38
+ @valid_config_keys = @config.keys
39
+
40
+ def self.configure(opts = {})
41
+ opts.each {|key,value| @config[key] = value if @valid_config_keys.include? key}
42
+ end
43
+
44
+ def self.config
45
+ @config
46
+ end
47
+
48
+ def self.money(price)
49
+ if price.include?("€")
50
+ Money.parse(price[-1,1]+price[0..-2])
51
+ else
52
+ Money.parse price
53
+ end
54
+ end
55
+
56
+ #Base error
57
+ class StewError < StandardError; end
58
+ end
@@ -0,0 +1,23 @@
1
+ module Stew
2
+ module Community
3
+
4
+ # Represents the base data for a Steam Profile
5
+ class Profile
6
+
7
+ attr_reader :id
8
+
9
+ attr_reader :nickname
10
+
11
+ def initialize(hash)
12
+ set_data(hash)
13
+ end
14
+
15
+ private
16
+
17
+ def set_data(data)
18
+ @id = data['steamID64'].to_i
19
+ @nickname = data['steamID']
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ module Stew
2
+ module Community
3
+
4
+ # Represents the friends of a Steam profile
5
+ # Enumerates a list of Steam Id instances
6
+ class ProfileFriends
7
+ include Enumerable
8
+
9
+ def initialize(data)
10
+ @friends = data.map {|friend| SteamId.new(friend)}
11
+ end
12
+
13
+ def each(&block)
14
+ @friends.each(&block)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ module Stew
2
+ module Community
3
+
4
+ #Represents a Steam Game owned by a Steam Id
5
+ class ProfileGame
6
+ attr_reader :app_id
7
+
8
+ attr_reader :name
9
+
10
+ attr_reader :logo
11
+
12
+ attr_reader :store_link
13
+
14
+ attr_reader :hours_last_2_weeks
15
+
16
+ attr_reader :hours_on_record
17
+
18
+ def initialize(hash)
19
+ @app_id = hash['appID'].to_i
20
+ @name = hash['name']
21
+ @logo = hash['logo']
22
+ @store_link = hash['storeLink']
23
+ @hours_last_2_weeks = hash['hoursLast2Weeks'].to_f
24
+ @hours_on_record = hash['hoursOnRecord'].to_f
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ module Stew
2
+ module Community
3
+
4
+ #Represents the ProfileGame instances owned by a steam id
5
+ class ProfileGames
6
+ include Enumerable
7
+
8
+ def initialize(data)
9
+ @games = Array.new(data.map{|game| ProfileGame.new(game)})
10
+ end
11
+
12
+ def each(&block)
13
+ @games.each(&block)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,52 @@
1
+ module Stew
2
+ module Community
3
+
4
+ # The base class for all Steam Ids. Has accessors for the base profile, games and friends of a Steam Id
5
+ #
6
+ # @example Create an instance from a 64-bit steam id
7
+ # Stew::Community::SteamId.new(76561197992917668) #=> Stew::Community::SteamId
8
+ #
9
+ # @example Create an instance from a community URL
10
+ # Stew::Community::SteamId.create("http://steamcommunity.com/profiles/76561197992917668") #=> Stew::Community::SteamId
11
+ #
12
+ class SteamId
13
+
14
+ attr_reader :id
15
+
16
+ def self.create(data)
17
+ return self.new($1.to_i) if data =~ /steamcommunity.com\/profiles\/([0-9]+)/
18
+ return self.new($1) if data =~ /steamcommunity.com\/id\/([a-zA-Z0-9]+)/
19
+ return self.new(data) if data.to_s.match /^[0-9A-Za-z]+$/
20
+ raise SteamIdNotFoundError
21
+ end
22
+
23
+ def initialize(steam_id,opts={})
24
+ @id = steam_id
25
+ @client = opts[:client] || self.class.client_with_options(steam_id)
26
+ end
27
+
28
+ def profile
29
+ @profile ||= @client.profile @id
30
+ end
31
+
32
+ def games
33
+ @games ||= @client.profile_games @id
34
+ end
35
+
36
+ def friends
37
+ @friends ||= @client.profile_friends @id
38
+ end
39
+
40
+ private
41
+
42
+ def self.client_with_options(steam_id)
43
+ klass = Stew.config[:default_community_client]
44
+ return klass.new if steam_id.to_s.match /^[0-9]+$/
45
+ return klass.new({:base_path => 'id'})
46
+ end
47
+ end
48
+
49
+ # Error to be raised when no Steam id is found
50
+ class SteamIdNotFoundError < StandardError; end
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ module Stew
2
+ # Creation of all profile* objects.
3
+ # Uses a given or default XmlClient instance to communicate with the Steam API
4
+ # The main identifier for most methods is the 64-bit steam id
5
+ # @example Create a Profile
6
+ # Stew::CommunityClient.new.profile(76561197992917668) #=> Stew::Community::Profile
7
+ #
8
+ # @example Resolve a Steam Vanity name
9
+ # Stew::CommunityClient.steam_id_from_vanity_name('eekon20') #=> 76561197986383225
10
+ #
11
+ class CommunityClient
12
+ COMMUNITY_URL = 'http://steamcommunity.com'
13
+ DEFAULT_BASE_PATH = 'profiles'
14
+
15
+ def self.steam_id_from_vanity_name(vanity_name)
16
+ Stew::XmlClient.new(COMMUNITY_URL).get("/id/#{vanity_name}")['profile']['steamID64'].to_i
17
+ end
18
+
19
+ def initialize(opts = {})
20
+ @xml_client = opts[:client] || Stew.config[:default_xml_client].new(COMMUNITY_URL)
21
+ @base_path = opts[:base_path] || DEFAULT_BASE_PATH
22
+ end
23
+
24
+ def profile(steam_id)
25
+ Community::Profile.new @xml_client.get(path(steam_id))['profile']
26
+ end
27
+
28
+ def profile_games(steam_id)
29
+ Community::ProfileGames.new @xml_client.get(path(steam_id,'games'))['gamesList']['games']['game']
30
+ end
31
+
32
+ def profile_friends(steam_id)
33
+ Community::ProfileFriends.new @xml_client.get(path(steam_id,'friends'))['friendsList']['friends']['friend']
34
+ end
35
+
36
+ private
37
+
38
+ def path(steam_id,command=nil)
39
+ "/#{@base_path}/#{steam_id}/#{command}"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,76 @@
1
+ module Stew
2
+ module Store
3
+
4
+ # An application in the Steam Store
5
+ # Initialized from the contents of a web request to the Steam store app page
6
+ class App
7
+ def initialize(response)
8
+ @document = Nokogiri::HTML(response)
9
+ end
10
+
11
+ def score
12
+ score_section[0] && score_from_score_section
13
+ end
14
+
15
+ def name
16
+ App.content_or_nil @document.at_css("div.apphub_AppName")
17
+ end
18
+
19
+ def release_date
20
+ node = @document.at_xpath("//b[.='Release Date:']")
21
+ node && Date.parse(node.next.content)
22
+ end
23
+
24
+ def genres
25
+ @document.css("div.glance_details").css("a").map {|node| node.content}
26
+ end
27
+
28
+ def developer
29
+ App.content_or_nil @document.at_xpath("//a[contains(@href, 'developer')]")
30
+ end
31
+
32
+ def publisher
33
+ App.content_or_nil @document.at_xpath("//a[contains(@href, 'publisher')]")
34
+ end
35
+
36
+ def offers
37
+ @offers ||= AppOffers.new @document.css("div.game_area_purchase_game")
38
+ end
39
+
40
+ def dlc?
41
+ @document.at_css("div.game_area_dlc_bubble")
42
+ end
43
+
44
+ def indie?
45
+ genres.include? 'Indie'
46
+ end
47
+
48
+ def price
49
+ return nil if free?
50
+ first_offer_price
51
+ end
52
+
53
+ def free?
54
+ offers.count == 0
55
+ end
56
+
57
+ private
58
+
59
+ def first_offer_price
60
+ offers.first.price
61
+ end
62
+
63
+ def self.content_or_nil(item)
64
+ item && item.content
65
+ end
66
+
67
+ def score_section
68
+ @document.xpath("//div[@id='game_area_metascore']")
69
+ end
70
+
71
+ def score_from_score_section
72
+ score_section[0].content.gsub(/[^0-9]/,'').to_i
73
+ end
74
+ end
75
+ end
76
+ end