xbox_live 0.3.3
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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +150 -0
- data/Rakefile +3 -0
- data/example-usage.rb +20 -0
- data/lib/xbox_live/achievement_info.rb +19 -0
- data/lib/xbox_live/achievements_page.rb +83 -0
- data/lib/xbox_live/game_info.rb +18 -0
- data/lib/xbox_live/games_page.rb +106 -0
- data/lib/xbox_live/profile_page.rb +91 -0
- data/lib/xbox_live/scraper.rb +153 -0
- data/lib/xbox_live/version.rb +3 -0
- data/lib/xbox_live.rb +27 -0
- data/spec/scraper_spec.rb +30 -0
- data/spec/spec_helper.rb +2 -0
- data/xbox_live.gemspec +24 -0
- metadata +94 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# XboxLive
|
|
2
|
+
|
|
3
|
+
XboxLive enables retrieval of player, game, and achievement data from
|
|
4
|
+
the Xbox Live web site.
|
|
5
|
+
|
|
6
|
+
## Status
|
|
7
|
+
|
|
8
|
+
This is an early pre-release version! The API is almost certain to
|
|
9
|
+
change before the 1.0 release.
|
|
10
|
+
|
|
11
|
+
Questions and suggestions are welcomed, as are pull requests.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Include the gem in your Gemfile:
|
|
16
|
+
|
|
17
|
+
gem "xbox_live"
|
|
18
|
+
|
|
19
|
+
Or, if you aren't using Bundler, just run:
|
|
20
|
+
|
|
21
|
+
gem install xbox_live
|
|
22
|
+
|
|
23
|
+
## Configuration
|
|
24
|
+
|
|
25
|
+
An Xbox Live username and password must be provided so that the gem
|
|
26
|
+
can log into the Xbox Live web site to retrieve data.
|
|
27
|
+
|
|
28
|
+
To configure these settings, include the following lines (substituting
|
|
29
|
+
your information) in your program, or for Rails applications, create
|
|
30
|
+
a `config/initializers/xbox_live.rb` file and add the lines there:
|
|
31
|
+
|
|
32
|
+
# Your Xbox Live login and password
|
|
33
|
+
XboxLive.options[:username] = 'your@email.address'
|
|
34
|
+
XboxLive.options[:password] = 'password'
|
|
35
|
+
|
|
36
|
+
Two optional configuration options are also available, but are not
|
|
37
|
+
required to be set:
|
|
38
|
+
|
|
39
|
+
# Pages retrieved from Xbox Live are cached for 10 minutes (600
|
|
40
|
+
# seconds) by default, to prevent unnecessary reloads from the Xbox
|
|
41
|
+
# Live web site. The maximum cache age can be changed here.
|
|
42
|
+
XboxLive.options[:refresh_age] = 300 # Cache for only 5 minutes
|
|
43
|
+
|
|
44
|
+
# Show debugging output on the console.
|
|
45
|
+
XboxLive.options[:debug] = true
|
|
46
|
+
|
|
47
|
+
## Example
|
|
48
|
+
|
|
49
|
+
Below is a short sample stand-alone program to demonstrate basic
|
|
50
|
+
functionality. This sample program is also included in the git
|
|
51
|
+
repository.
|
|
52
|
+
|
|
53
|
+
require 'xbox_live'
|
|
54
|
+
|
|
55
|
+
# Your Xbox Live login and password
|
|
56
|
+
XboxLive.options[:username] = 'your@email.address'
|
|
57
|
+
XboxLive.options[:password] = 'password'
|
|
58
|
+
|
|
59
|
+
player = 'gamertag'
|
|
60
|
+
|
|
61
|
+
profile_page = XboxLive::ProfilePage.new(player)
|
|
62
|
+
puts "Gamerscore: #{profile_page.gamerscore}"
|
|
63
|
+
|
|
64
|
+
games_page = XboxLive::GamesPage.new(player)
|
|
65
|
+
first_game = games_page.games.first
|
|
66
|
+
puts "Score in '#{first_game.name}': #{first_game.unlocked_points} out of #{first_game.total_points}"
|
|
67
|
+
|
|
68
|
+
achievements_page = XboxLive::AchievementsPage.new(player, first_game.id)
|
|
69
|
+
first_ach = achievements_page.achievements.first
|
|
70
|
+
puts "Unlocked achievement '#{first_ach.name}' on #{first_ach.unlocked_on}"
|
|
71
|
+
|
|
72
|
+
This will output something along these lines (depending on the gamertag
|
|
73
|
+
entered for the `player` variable:
|
|
74
|
+
|
|
75
|
+
Gamerscore: 9454.
|
|
76
|
+
Score in 'Battlefield 3': 100 out of 1000.
|
|
77
|
+
Unlocked achievement '1st Loser' on 10/28/2011.
|
|
78
|
+
|
|
79
|
+
## Available Data
|
|
80
|
+
|
|
81
|
+
The `XboxLive::ProfilePage` class makes the following data available
|
|
82
|
+
from a player's Profile page, via a call like `profile_page =
|
|
83
|
+
XboxLive::ProfilePage.new(gamertag)`:
|
|
84
|
+
|
|
85
|
+
* `profile_page.gamertag`
|
|
86
|
+
* `profile_page.gamerscore`
|
|
87
|
+
* `profile_page.motto`
|
|
88
|
+
* `profile_page.avatar`
|
|
89
|
+
* `profile_page.gamertile_small`
|
|
90
|
+
* `profile_page.nickname`
|
|
91
|
+
* `profile_page.bio`
|
|
92
|
+
* `profile_page.presence`
|
|
93
|
+
|
|
94
|
+
The `XboxLive::GamesPage` class makes the following data available from
|
|
95
|
+
a player's Game Comparison page, via a call like `games_page =
|
|
96
|
+
XboxLive::GamesPage.new(gamertag)`:
|
|
97
|
+
|
|
98
|
+
* `games_page.gamertag`
|
|
99
|
+
* `games_page.gamertile_large`
|
|
100
|
+
* `games_page.gamerscore`
|
|
101
|
+
* `games_page.progress`
|
|
102
|
+
* `games_page.games` _(see below)_
|
|
103
|
+
|
|
104
|
+
`games_page.games` is an Array of XboxLive::GameInfo instances, which track information about a
|
|
105
|
+
player's progress in a game. Each GameInfo instance makes the following data available:
|
|
106
|
+
|
|
107
|
+
* `game_info.id` - unique Microsoft identifier
|
|
108
|
+
* `game_info.name`
|
|
109
|
+
* `game_info.tile`
|
|
110
|
+
* `game_info.total_points`
|
|
111
|
+
* `game_info.total_achievements`
|
|
112
|
+
* `game_info.gamertag`
|
|
113
|
+
* `game_info.unlocked_points`
|
|
114
|
+
* `game_info.unlocked_achievements`
|
|
115
|
+
* `game_info.last_played`
|
|
116
|
+
|
|
117
|
+
The `XboxLive::AchievementsPage` class makes the following data
|
|
118
|
+
available from a player's Game Achievement Comparison page, via a call
|
|
119
|
+
like `ach_page = XboxLive::AchievementsPage.new(gamertag, game_id)`:
|
|
120
|
+
|
|
121
|
+
* `ach_page.gamertag`
|
|
122
|
+
* `ach_page.game_id`
|
|
123
|
+
* `ach_page.achievements` _(see below)_
|
|
124
|
+
|
|
125
|
+
`ach_page.achievements` is an Array of XboxLive::AchievementInfo
|
|
126
|
+
instances, which track information about a player's achievements in a
|
|
127
|
+
game. Each AchievementInfo instance makes the following data available:
|
|
128
|
+
|
|
129
|
+
* `ach_info.id` - Microsoft identifier, unique only within this game
|
|
130
|
+
* `ach_info.gamertag`
|
|
131
|
+
* `ach_info.game_id`
|
|
132
|
+
* `ach_info.name`
|
|
133
|
+
* `ach_info.description`
|
|
134
|
+
* `ach_info.tile`
|
|
135
|
+
* `ach_info.points`
|
|
136
|
+
* `ach_info.unlocked_at` - nil if the player has not yet unlocked it
|
|
137
|
+
|
|
138
|
+
## Caveats
|
|
139
|
+
|
|
140
|
+
The contents, layout, and authentication scheme for the Xbox Live web
|
|
141
|
+
site may change at any time, and historically has changed several times
|
|
142
|
+
per year. These changes will almost certainly break the functionality
|
|
143
|
+
of this gem, requiring a new version of the gem to be coded and
|
|
144
|
+
released.
|
|
145
|
+
|
|
146
|
+
## To Do for Version 1.0
|
|
147
|
+
|
|
148
|
+
* Write tests
|
|
149
|
+
* Refactor
|
|
150
|
+
* Improve API
|
data/Rakefile
ADDED
data/example-usage.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'xbox_live'
|
|
2
|
+
|
|
3
|
+
# Your Xbox Live login and password
|
|
4
|
+
XboxLive.options[:username] = 'your@email.address'
|
|
5
|
+
XboxLive.options[:password] = 'password'
|
|
6
|
+
XboxLive.options[:debug] = false
|
|
7
|
+
|
|
8
|
+
player = 'gamertag'
|
|
9
|
+
|
|
10
|
+
profile_page = XboxLive::ProfilePage.new(player)
|
|
11
|
+
puts "Gamerscore: #{profile_page.gamerscore}"
|
|
12
|
+
|
|
13
|
+
games_page = XboxLive::GamesPage.new(player)
|
|
14
|
+
first_game = games_page.games.first
|
|
15
|
+
puts "Score in '#{first_game.name}': #{first_game.unlocked_points} out of #{first_game.total_points}"
|
|
16
|
+
|
|
17
|
+
achievements_page = XboxLive::AchievementsPage.new(player, first_game.id)
|
|
18
|
+
first_ach = achievements_page.achievements.first
|
|
19
|
+
puts "Unlocked achievement '#{first_ach.name}' on #{first_ach.unlocked_on}"
|
|
20
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module XboxLive
|
|
2
|
+
|
|
3
|
+
# Each AchievementInfo tracks information about a player's progress in a
|
|
4
|
+
# specific achievement.
|
|
5
|
+
class AchievementInfo
|
|
6
|
+
|
|
7
|
+
attr_accessor :id, :game_id, :gamertag, :name, :description, :tile, :points, :unlocked_at
|
|
8
|
+
|
|
9
|
+
# Create a new AchievementInfo for the provided player and game.
|
|
10
|
+
def initialize(gamertag, game_id, achievement_id)
|
|
11
|
+
@gamertag = gamertag
|
|
12
|
+
@game_id = game_id
|
|
13
|
+
@id = achievement_id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module XboxLive
|
|
2
|
+
|
|
3
|
+
# Each AchievementsPage tracks and makes available the data contianed
|
|
4
|
+
# in an Xbox Live "Compare Game" page. This can be used to determine
|
|
5
|
+
# which achievements a player has unlocked in a game.
|
|
6
|
+
#
|
|
7
|
+
# Example: http://live.xbox.com/en-US/Activity/Details?titleId=1161890128&compareTo=someone
|
|
8
|
+
class AchievementsPage
|
|
9
|
+
|
|
10
|
+
attr_accessor :gamertag, :game_id, :page, :url, :updated_at, :achievements, :data
|
|
11
|
+
|
|
12
|
+
# Create a new AchievementsPage for the provided gamertag. Retrieve
|
|
13
|
+
# the html compare achievements page from the Xbox Live web site for
|
|
14
|
+
# analysis. To prevent multiple instances for the same gamertag,
|
|
15
|
+
# this method is marked as private. The AchievementsPage.find()
|
|
16
|
+
# method should be used to find an existing instance or create a new
|
|
17
|
+
# one if needed.
|
|
18
|
+
def initialize(gamertag, game_id)
|
|
19
|
+
@gamertag = gamertag
|
|
20
|
+
@game_id = game_id
|
|
21
|
+
refresh
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Force a reload of the AchievementsPage data from the Xbox Live web site.
|
|
26
|
+
def refresh
|
|
27
|
+
url = XboxLive.options[:url_prefix] + '/en-US/Activity/Details?' +
|
|
28
|
+
Mechanize::Util.build_query_string(titleId: @game_id, compareTo: @gamertag)
|
|
29
|
+
@page = XboxLive::Scraper::get_page url
|
|
30
|
+
return false if page.nil?
|
|
31
|
+
|
|
32
|
+
@url = url
|
|
33
|
+
@updated_at = Time.now
|
|
34
|
+
@data = retrieve_achievement_data
|
|
35
|
+
@gamertag = find_gamertag
|
|
36
|
+
@achievements = find_achievements
|
|
37
|
+
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# POST to retrieve the JSON data about achievements for this game
|
|
45
|
+
def retrieve_achievement_data
|
|
46
|
+
data = @page.body.match(/loadCompareView\((.+)\)\;/)[1]
|
|
47
|
+
JSON.parse(data)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Find the gamertag, in case the caps/lowercase are different than
|
|
51
|
+
# what was provided.
|
|
52
|
+
def find_gamertag
|
|
53
|
+
player = @data['Players'].find { |p| p['Gamertag'].casecmp(@gamertag) == 0 }
|
|
54
|
+
player ? player['Gamertag'] : nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Find and return an array of hashes containing information about each
|
|
58
|
+
# achievement the player has unlocked.
|
|
59
|
+
def find_achievements
|
|
60
|
+
achievements = @data['Achievements'].collect do |ach|
|
|
61
|
+
ai = AchievementInfo.new(gamertag, @game_id, ach['Id'])
|
|
62
|
+
ai.name = ach['Name']
|
|
63
|
+
ai.description = ach['Description']
|
|
64
|
+
ai.tile = ach['TileUrl']
|
|
65
|
+
if unlocked?(ach)
|
|
66
|
+
ai.points = ach['Score']
|
|
67
|
+
# TODO: Refactor this mess
|
|
68
|
+
time_field = ach['EarnDates'][@gamertag]['EarnedOn'].match(/Date\((\d+)/)
|
|
69
|
+
ai.unlocked_at = Time.at(time_field[1].to_i / 1000) if time_field
|
|
70
|
+
end
|
|
71
|
+
ai
|
|
72
|
+
end
|
|
73
|
+
achievements
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Has the player unlocked this achievement?
|
|
77
|
+
def unlocked?(ach)
|
|
78
|
+
!!ach['EarnDates'][@gamertag]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module XboxLive
|
|
2
|
+
|
|
3
|
+
# Each GameInfo tracks information about a player's progress in a
|
|
4
|
+
# specific game.
|
|
5
|
+
class GameInfo
|
|
6
|
+
|
|
7
|
+
attr_accessor :id, :name, :tile, :gamertag, :total_points, :unlocked_points,
|
|
8
|
+
:total_achievements, :unlocked_achievements, :last_played
|
|
9
|
+
|
|
10
|
+
# Create a new GameInfo for the provided player and game.
|
|
11
|
+
def initialize(gamertag, game_id)
|
|
12
|
+
@gamertag = gamertag
|
|
13
|
+
@id = game_id
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module XboxLive
|
|
2
|
+
|
|
3
|
+
# Each GamesPage tracks the data contianed in an Xbox Live "Compare
|
|
4
|
+
# Games" page. This can be used to determine which games a player has
|
|
5
|
+
# played, and their score and number of achievements acquired in each
|
|
6
|
+
# game.
|
|
7
|
+
#
|
|
8
|
+
# Example: http://live.xbox.com/en-US/Activity?compareTo=someone
|
|
9
|
+
class GamesPage
|
|
10
|
+
|
|
11
|
+
attr_accessor :gamertag, :page, :url, :updated_at, :gamertile_large,
|
|
12
|
+
:gamerscore, :progress, :games, :data
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Create a new GamesPage for the provided gamertag. Retrieve the
|
|
16
|
+
# html game compare page from the Xbox Live web site for analysis.
|
|
17
|
+
def initialize(gamertag)
|
|
18
|
+
@gamertag = gamertag
|
|
19
|
+
refresh
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Force a reload of the GamesPage data from the Xbox Live web site.
|
|
24
|
+
def refresh
|
|
25
|
+
url = XboxLive.options[:url_prefix] + '/en-US/Activity?' +
|
|
26
|
+
Mechanize::Util.build_query_string(compareTo: @gamertag)
|
|
27
|
+
@page = XboxLive::Scraper::get_page(url)
|
|
28
|
+
return false if page.nil?
|
|
29
|
+
|
|
30
|
+
@url = url
|
|
31
|
+
@updated_at = Time.now
|
|
32
|
+
@data = retrieve_game_data
|
|
33
|
+
@gamertag = find_gamertag
|
|
34
|
+
@gamertile_large = find_gamertile_large
|
|
35
|
+
@gamerscore = find_gamerscore
|
|
36
|
+
@progress = find_progress
|
|
37
|
+
@games = find_games
|
|
38
|
+
|
|
39
|
+
return true
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
# POST to retrieve the JSON data about games that have been played
|
|
46
|
+
def retrieve_game_data
|
|
47
|
+
if token = find_request_verification_token
|
|
48
|
+
url = XboxLive.options[:url_prefix] + '/en-US/Activity/Summary?' +
|
|
49
|
+
Mechanize::Util.build_query_string(compareTo: @gamertag)
|
|
50
|
+
page = XboxLive::Scraper::post_page(url, '__RequestVerificationToken' => token)
|
|
51
|
+
end
|
|
52
|
+
JSON.parse(page.body)['Data']
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Find the RequestVerificationToken
|
|
56
|
+
def find_request_verification_token
|
|
57
|
+
token_block = @page.at('input[name=__RequestVerificationToken]')
|
|
58
|
+
token_block ? token_block.get_attribute('value') : nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Find the gamertag, in case the caps/lowercase are different than
|
|
62
|
+
# what was provided.
|
|
63
|
+
def find_gamertag
|
|
64
|
+
player = @data['Players'].find { |p| p['Gamertag'].casecmp(@gamertag) == 0 }
|
|
65
|
+
player ? player['Gamertag'] : nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Find and return the player's large gamertile url from the Games data
|
|
69
|
+
def find_gamertile_large
|
|
70
|
+
player = @data['Players'].find { |p| p['Gamertag'] == @gamertag }
|
|
71
|
+
player ? player['Gamerpic'] : nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Find and return the player's gamerscore from the Games page
|
|
75
|
+
def find_gamerscore
|
|
76
|
+
player = @data['Players'].find { |p| p['Gamertag'] == @gamertag }
|
|
77
|
+
player ? player['Gamerscore'] : nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Find and return the player's game progress statistic from the Games page
|
|
81
|
+
def find_progress
|
|
82
|
+
player = @data['Players'].find { |p| p['Gamertag'] == @gamertag }
|
|
83
|
+
player ? player['PercentComplete'] : nil
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Find and return an array of hashes containing information about each
|
|
87
|
+
# game the player has played.
|
|
88
|
+
def find_games
|
|
89
|
+
games = @data['Games'].collect do |game|
|
|
90
|
+
gi = GameInfo.new(gamertag, game['Id'])
|
|
91
|
+
gi.name = game['Name']
|
|
92
|
+
gi.tile = game['BoxArt']
|
|
93
|
+
gi.total_points = game['PossibleScore']
|
|
94
|
+
gi.total_achievements = game['PossibleAchievements']
|
|
95
|
+
gi.unlocked_points = game['Progress'][@gamertag]['Score']
|
|
96
|
+
gi.unlocked_achievements = game['Progress'][@gamertag]['Achievements']
|
|
97
|
+
time_field = game['Progress'][@gamertag]['LastPlayed'].match(/Date\((\d+)/)
|
|
98
|
+
gi.last_played = Time.at(time_field[1].to_i / 1000) if time_field
|
|
99
|
+
gi
|
|
100
|
+
end
|
|
101
|
+
return games
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module XboxLive
|
|
2
|
+
|
|
3
|
+
# Each ProfilePage tracks and makes available the data contained in an Xbox
|
|
4
|
+
# Live profile web page. This can be used to determine general information
|
|
5
|
+
# about a payer, such as their total score, avatar picture, or bio.
|
|
6
|
+
#
|
|
7
|
+
# Example: http://live.xbox.com/en-US/Profile?Gamertag=someone
|
|
8
|
+
class ProfilePage
|
|
9
|
+
|
|
10
|
+
attr_accessor :gamertag, :page, :url, :updated_at, :gamerscore, :motto,
|
|
11
|
+
:avatar, :gamertile_small, :nickname, :bio, :presence
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Create a new ProfilePage for the provided gamertag. Retrieve the
|
|
15
|
+
# html profile page from the Xbox Live web site for analysis.
|
|
16
|
+
def initialize(gamertag)
|
|
17
|
+
@gamertag = gamertag
|
|
18
|
+
refresh
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Force a reload of the ProfilePage data from the Xbox Live web site.
|
|
23
|
+
#
|
|
24
|
+
# TODO: Parse the Location: and reputation fields as well.
|
|
25
|
+
def refresh
|
|
26
|
+
url = XboxLive.options[:url_prefix] + '/en-US/Profile?' +
|
|
27
|
+
Mechanize::Util.build_query_string(gamertag: @gamertag)
|
|
28
|
+
@page = XboxLive::Scraper::get_page url
|
|
29
|
+
return false if @page.nil?
|
|
30
|
+
|
|
31
|
+
@url = url
|
|
32
|
+
@updated_at = Time.now
|
|
33
|
+
@gamerscore = find_gamerscore
|
|
34
|
+
@motto = find_motto
|
|
35
|
+
@avatar = find_avatar
|
|
36
|
+
@nickname = find_nickname
|
|
37
|
+
@bio = find_bio
|
|
38
|
+
@presence = find_presence
|
|
39
|
+
@gamertile_small = find_gamertile_small
|
|
40
|
+
|
|
41
|
+
return true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
# Find and return the player's gamerscore from the ProfilePage
|
|
47
|
+
def find_gamerscore
|
|
48
|
+
score_block = @page.at('div.gamerscore')
|
|
49
|
+
score_block ? score_block.inner_html.to_i : nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Find and return the player's motto from the ProfilePage
|
|
53
|
+
def find_motto
|
|
54
|
+
motto_block = @page.at('div.motto')
|
|
55
|
+
# TODO: Need to strip out the empty bubble-arrow div
|
|
56
|
+
motto_block ? motto_block.inner_html.strip : nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Find and return the player's avatar url from the ProfilePage
|
|
60
|
+
def find_avatar
|
|
61
|
+
# FIXME: Not currently working. Javascript?
|
|
62
|
+
avatar_block = @page.at('img.bodyshot')
|
|
63
|
+
avatar_block ? avatar_block.get_attribute('src') : nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Find and return the player's small gamertile url from the ProfilePage
|
|
67
|
+
def find_gamertile_small
|
|
68
|
+
tile_block = @page.at('img.gamerpic')
|
|
69
|
+
tile_block ? tile_block.get_attribute('src') : nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Find and return the player's nickname from the ProfilePage
|
|
73
|
+
def find_nickname
|
|
74
|
+
nickname_block = @page.at('div.name div.value')
|
|
75
|
+
nickname_block ? nickname_block.inner_html.strip : nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Find and return the player's bio from the ProfilePage
|
|
79
|
+
def find_bio
|
|
80
|
+
bio_block = @page.at('div.bio div.value')
|
|
81
|
+
bio_block ? bio_block.inner_html.strip : nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Find and return the player's most recent presence info from the ProfilePage
|
|
85
|
+
def find_presence
|
|
86
|
+
presence_block = @page.at('div.presence')
|
|
87
|
+
presence_block ? presence_block.inner_html.strip : nil
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
module XboxLive
|
|
2
|
+
|
|
3
|
+
# Scraper is a collection of methods to log into the Xbox Live web site
|
|
4
|
+
# and retrieve web pages.
|
|
5
|
+
#
|
|
6
|
+
# The only public function is XboxLive::Scraper.get_page(url)
|
|
7
|
+
module Scraper
|
|
8
|
+
|
|
9
|
+
# Since loading pages from the Xbox Live web site is expensive
|
|
10
|
+
# (slow), pages should be cached for a short amount of time in case
|
|
11
|
+
# they are re-requested again.
|
|
12
|
+
@cache = Hash.new
|
|
13
|
+
|
|
14
|
+
# Load a page from Xbox Live and return a Mechanize/Nokogiri page
|
|
15
|
+
# TODO: cache pages for some time to prevent duplicative HTTP activity
|
|
16
|
+
def self.get_page(url)
|
|
17
|
+
log "Loading page #{url}."
|
|
18
|
+
|
|
19
|
+
# Check to see if there is a recent version of the page in cache
|
|
20
|
+
if @cache[url]
|
|
21
|
+
log " Found page in cache."
|
|
22
|
+
return @cache[url][:page] if Time.now - @cache[url][:updated_at] < XboxLive.options[:refresh_age]
|
|
23
|
+
log " but the cached page is stale."
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Load the specified page via Mechanize
|
|
27
|
+
log " Getting page from Xbox Live."
|
|
28
|
+
page = safe_get(url)
|
|
29
|
+
|
|
30
|
+
# Most pages require authentication. If the Mechanize agent has
|
|
31
|
+
# not logged in yet, or if the session has expired, it will be
|
|
32
|
+
# redirected to the Xbox Live login page.
|
|
33
|
+
if login_page?(page)
|
|
34
|
+
# Log the agent in via the returned login page.
|
|
35
|
+
log " Page load failed - not signed in."
|
|
36
|
+
page = login(page)
|
|
37
|
+
|
|
38
|
+
# The login SHOULD have returned the original page requested,
|
|
39
|
+
# but the URL will be the POST URL, so there is no way to be
|
|
40
|
+
# certain. Therefore, it is safest to just load the page again
|
|
41
|
+
# now that the Mechanize agent has logged in.
|
|
42
|
+
log " Retrying page #{url}"
|
|
43
|
+
page = safe_get(url)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if page.nil? or page.title.match /Error/
|
|
47
|
+
log " ERROR: failed to load page. Trying again."
|
|
48
|
+
page = safe_get(url)
|
|
49
|
+
if page.nil? or page.title.match /Error/
|
|
50
|
+
log " ERROR: failed on second try. Aborting."
|
|
51
|
+
return nil
|
|
52
|
+
else
|
|
53
|
+
log " SUCCESS: page loaded on retry."
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if page.uri.to_s != url
|
|
58
|
+
log " ERROR: loaded page URL does not match expected URL. Loaded: #{page.uri.to_s}"
|
|
59
|
+
return nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
log " Loaded page '#{page.title.strip}'. Storing in cache."
|
|
63
|
+
@cache[url] = { page: page, updated_at: Time.now }
|
|
64
|
+
page
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# POST a page to Xbox Live and return the result.
|
|
68
|
+
def self.post_page(url, params)
|
|
69
|
+
log "POSTing page #{url} with params #{params}."
|
|
70
|
+
page = agent.post(url, params)
|
|
71
|
+
page
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# private
|
|
76
|
+
|
|
77
|
+
# Get a page, but catch any errors so processing can continue
|
|
78
|
+
def self.safe_get(page)
|
|
79
|
+
begin
|
|
80
|
+
return agent.get(page)
|
|
81
|
+
# rescue Errno::ETIMEDOUT, Timeout::Error, Mechanize::ResponseCodeError
|
|
82
|
+
rescue
|
|
83
|
+
return nil
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Log in to Xbox Live using the supplied login page.
|
|
88
|
+
def self.login(page)
|
|
89
|
+
return nil if !login_page?(page)
|
|
90
|
+
|
|
91
|
+
# Find the URL where the login form should be POSTed to.
|
|
92
|
+
url = page.body.match(/srf_uPost='([^']+)/)[1]
|
|
93
|
+
if url.empty?
|
|
94
|
+
log " ERROR: Trying to log in but 'Sign In' page doesn't contain needed info."
|
|
95
|
+
return nil
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# PPFT appears to be some kind of session identifier which is
|
|
99
|
+
# required for the login process.
|
|
100
|
+
ppft_html = page.body.match(/srf_sFT='([^']+)/)[1]
|
|
101
|
+
ppft = ppft_html.match(/value="([^"]+)/)[1]
|
|
102
|
+
|
|
103
|
+
# The rest of the parameters are either user-provided (i.e.
|
|
104
|
+
# username and password) or are constants.
|
|
105
|
+
params = {
|
|
106
|
+
'login' => XboxLive.options[:username],
|
|
107
|
+
'passwd' => XboxLive.options[:password],
|
|
108
|
+
'type' => '11',
|
|
109
|
+
'LoginOptions' => '3',
|
|
110
|
+
'NewUser' => '1',
|
|
111
|
+
'PPSX' => 'Passpor',
|
|
112
|
+
'PPFT' => ppft,
|
|
113
|
+
'idshbo' => '1'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# POST the login form and hope for the best.
|
|
117
|
+
log " Submitting login form via POST"
|
|
118
|
+
page = agent.post(url, params)
|
|
119
|
+
|
|
120
|
+
# The login will fail and return a page saying that Javascript must be
|
|
121
|
+
# enabled. However, there is a hidden form in the page that can be
|
|
122
|
+
# submitted to enable non-javascript support.
|
|
123
|
+
form = page.form('fmHF')
|
|
124
|
+
if form.nil?
|
|
125
|
+
log " ERROR: The non-JS login page doesn't contain form fmHF."
|
|
126
|
+
return nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Submitting the form on the Javascript error page completes the
|
|
130
|
+
# login process, and SHOULD return the originally requested page.
|
|
131
|
+
log " Submitting final non-JS login form"
|
|
132
|
+
agent.submit(form)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Check to see if the provided page the Xbox Live login page.
|
|
136
|
+
def self.login_page?(page)
|
|
137
|
+
page and page.title == "Welcome to Windows Live"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Create and memoize the Mechanize agent
|
|
141
|
+
def self.agent
|
|
142
|
+
log " Initializing mechanize agent @ #{Time.now.to_s}" if !defined? @@agent
|
|
143
|
+
@@agent ||= Mechanize.new { |a| a.user_agent_alias = 'Mac Safari' }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Write out a log entry
|
|
147
|
+
def self.log(message)
|
|
148
|
+
puts message if XboxLive.options[:debug]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
end
|
data/lib/xbox_live.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'mechanize'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'xbox_live/version'
|
|
4
|
+
require 'xbox_live/scraper'
|
|
5
|
+
require 'xbox_live/game_info'
|
|
6
|
+
require 'xbox_live/achievement_info'
|
|
7
|
+
require 'xbox_live/profile_page'
|
|
8
|
+
require 'xbox_live/games_page'
|
|
9
|
+
require 'xbox_live/achievements_page'
|
|
10
|
+
|
|
11
|
+
module XboxLive
|
|
12
|
+
|
|
13
|
+
# Provides configurability.
|
|
14
|
+
def self.options
|
|
15
|
+
@options ||= {
|
|
16
|
+
:username => nil,
|
|
17
|
+
:password => nil,
|
|
18
|
+
:refresh_age => 600, # data will be re-fetched if older than X seconds
|
|
19
|
+
:url_prefix => 'http://live.xbox.com'
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
XboxLive.options[:username] = 'xboxlive@mfischer.com'
|
|
24
|
+
XboxLive.options[:password] = 's9dALtmG'
|
|
25
|
+
XboxLive.options[:debug] = true
|
|
26
|
+
|
|
27
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe XboxLive::Scraper do
|
|
4
|
+
|
|
5
|
+
describe "#get_page" do
|
|
6
|
+
|
|
7
|
+
context "with a public (non-authenticated) page" do
|
|
8
|
+
before { @page = XboxLive::Scraper.get_page('http://live.xbox.com/en-US/MyXbox/Profile?gamertag=major%20nelson') }
|
|
9
|
+
|
|
10
|
+
it 'should return a Mechanize::Page instance' do
|
|
11
|
+
@page.class.should == Mechanize::Page
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'should return the page with the expected title' do
|
|
15
|
+
@page.title.strip.should == 'Major Nelson - Xbox.com'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
context "with a private (authenticated) page" do
|
|
21
|
+
before { @page = XboxLive::Scraper.get_page('http://live.xbox.com/en-US/MyXbox/Profile?gamertag=major%20nelson') }
|
|
22
|
+
|
|
23
|
+
it 'should return the page with the expected title' do
|
|
24
|
+
@page.title.strip.should == 'Major Nelson - Xbox.com'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/xbox_live.gemspec
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "xbox_live/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "xbox_live"
|
|
7
|
+
s.version = XboxLive::VERSION
|
|
8
|
+
s.authors = ["Mike Fischer"]
|
|
9
|
+
s.email = ["mikefischer99@gmail.com"]
|
|
10
|
+
s.homepage = "https://github.com/greendog99/xbox_live"
|
|
11
|
+
s.summary = %q{Xbox Live data retrieval}
|
|
12
|
+
s.description = %q{Log into Xbox Live and retrieve information about a player}
|
|
13
|
+
s.platform = Gem::Platform::RUBY
|
|
14
|
+
s.rubyforge_project = "xbox_live"
|
|
15
|
+
|
|
16
|
+
s.files = `git ls-files`.split("\n")
|
|
17
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
19
|
+
s.require_paths = ["lib"]
|
|
20
|
+
|
|
21
|
+
s.add_runtime_dependency "mechanize", "~> 1.0"
|
|
22
|
+
s.add_runtime_dependency "json"
|
|
23
|
+
s.add_development_dependency "rspec", "~> 2.6"
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: xbox_live
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.3.3
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Mike Fischer
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2011-11-13 00:00:00.000000000Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: mechanize
|
|
16
|
+
requirement: &70358515052260 !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ~>
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '1.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: *70358515052260
|
|
25
|
+
- !ruby/object:Gem::Dependency
|
|
26
|
+
name: json
|
|
27
|
+
requirement: &70358515051320 !ruby/object:Gem::Requirement
|
|
28
|
+
none: false
|
|
29
|
+
requirements:
|
|
30
|
+
- - ! '>='
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: *70358515051320
|
|
36
|
+
- !ruby/object:Gem::Dependency
|
|
37
|
+
name: rspec
|
|
38
|
+
requirement: &70358515050680 !ruby/object:Gem::Requirement
|
|
39
|
+
none: false
|
|
40
|
+
requirements:
|
|
41
|
+
- - ~>
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '2.6'
|
|
44
|
+
type: :development
|
|
45
|
+
prerelease: false
|
|
46
|
+
version_requirements: *70358515050680
|
|
47
|
+
description: Log into Xbox Live and retrieve information about a player
|
|
48
|
+
email:
|
|
49
|
+
- mikefischer99@gmail.com
|
|
50
|
+
executables: []
|
|
51
|
+
extensions: []
|
|
52
|
+
extra_rdoc_files: []
|
|
53
|
+
files:
|
|
54
|
+
- .gitignore
|
|
55
|
+
- Gemfile
|
|
56
|
+
- README.md
|
|
57
|
+
- Rakefile
|
|
58
|
+
- example-usage.rb
|
|
59
|
+
- lib/xbox_live.rb
|
|
60
|
+
- lib/xbox_live/achievement_info.rb
|
|
61
|
+
- lib/xbox_live/achievements_page.rb
|
|
62
|
+
- lib/xbox_live/game_info.rb
|
|
63
|
+
- lib/xbox_live/games_page.rb
|
|
64
|
+
- lib/xbox_live/profile_page.rb
|
|
65
|
+
- lib/xbox_live/scraper.rb
|
|
66
|
+
- lib/xbox_live/version.rb
|
|
67
|
+
- spec/scraper_spec.rb
|
|
68
|
+
- spec/spec_helper.rb
|
|
69
|
+
- xbox_live.gemspec
|
|
70
|
+
homepage: https://github.com/greendog99/xbox_live
|
|
71
|
+
licenses: []
|
|
72
|
+
post_install_message:
|
|
73
|
+
rdoc_options: []
|
|
74
|
+
require_paths:
|
|
75
|
+
- lib
|
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
77
|
+
none: false
|
|
78
|
+
requirements:
|
|
79
|
+
- - ! '>='
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
83
|
+
none: false
|
|
84
|
+
requirements:
|
|
85
|
+
- - ! '>='
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
requirements: []
|
|
89
|
+
rubyforge_project: xbox_live
|
|
90
|
+
rubygems_version: 1.8.10
|
|
91
|
+
signing_key:
|
|
92
|
+
specification_version: 3
|
|
93
|
+
summary: Xbox Live data retrieval
|
|
94
|
+
test_files: []
|