about summary refs log tree commit diff stats
path: root/scrape.rb
blob: 8ba4b68cd20326113a5e70c8a9cf395c5ea6730c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
require 'json'
require 'open-uri'
require 'yaml'

require 'rubygems'
require 'bundler/setup'
Bundler.require :default

MOON_COLORS = [
  :blue,
  :brown,
  :cyan,
  :green,
  :orange,
  :pink,
  :purple,
  :red,
  :star,
  :white,
  :yellow
]

@config = YAML.load(open(ARGV[0]))
db_existed = File.exists?(@config["database"])
db = Sequel.connect("sqlite://" + @config["database"])

if ARGV[1] == "init"
  if db_existed
    raise "Datafile already exists"
  end

  schema = File.read("schema.sql")

  db.run schema

  puts "Initialized datafile"

  exit
end

class Profile < Sequel::Model
  many_to_many :achievements, join_table: :dids
end

class Game < Sequel::Model
  one_to_many :achievements
  one_to_many :images
end

class Achievement < Sequel::Model
  many_to_one :game
  many_to_many :profiles, join_table: :dids
end

class Image < Sequel::Model
  many_to_one :game
end

class Did < Sequel::Model
  many_to_one :profile
  many_to_one :achievement
end

def scrape_profile(profile, full)
  if full
    url = "https://steamcommunity.com/#{profile.profile_path}/games/?tab=all"
  else
    url = "https://steamcommunity.com/#{profile.profile_path}/games/"
  end

  page = Nokogiri::HTML(open(url))
  script = page.css(".responsive_page_template_content script").text[18..-1]
  data = JSON.parse(script[0..script.index(";\r\n\t\t")-1])
  ids = data.map { |d| d["appid"] }

  index = 0
  ids.each do |id|
    index += 1
    puts "#{profile.profile_path} - #{index}/#{ids.count}"

    achsp = Nokogiri::HTML(
      open("https://steamcommunity.com/#{profile.profile_path}/stats/#{id}/"))

    achsp.css(".achieveTxt").each do |node|
      unless node.css(".achieveUnlockTime").empty?
        if Game.where(steam_appid: id).count > 0
          game = Game.where(steam_appid: id).first
        else
          moon_index = Random.rand(MOON_COLORS.size)

          game = Game.new(steam_appid: id, color: MOON_COLORS[moon_index])
          game.save

          # The cookie is required for games that have an age restriction
          storepage = Nokogiri::HTML(
            open(
              "http://store.steampowered.com/app/#{id}",
              "Cookie" => 'birthtime=126248401'))

          img_id = 0
          storepage.css(".highlight_screenshot_link").each do |node|
            begin
              imagepage = open(node["href"]).read

              img_id += 1
              img_filename = "#{id}-#{img_id}.jpg"
              img_filepath = File.join(@config["images"], img_filename)

              img_file = File.open(img_filepath, "w")
              img_file.write(imagepage)
              img_file.close

              image = Image.new(game: game, filename: img_filename)
              image.save
            rescue OpenURI::HTTPError
              puts "Error downloading an image"
            end

            sleep 2
          end
        end

        title = node.at_css("h3").text

        if game.achievements_dataset.where(title: title).count > 0
          achievement = game.achievements_dataset.where(title: title).first
        else
          achievement = Achievement.new(game: game, title: title)
          achievement.save
        end

        unless Did.where(profile: profile, achievement: achievement).count > 0
          begin
            unlock = DateTime.strptime(
              node.css(".achieveUnlockTime").text.lstrip[9..-1],
              "%b %d, %Y @ %l:%M%P")
          rescue ArgumentError
            unlock = DateTime.strptime(
              node.css(".achieveUnlockTime").text.lstrip[9..-1],
              "%b %d @ %l:%M%P")
          end

          join = Did.new(
            profile: profile,
            achievement: achievement,
            achieved_at: unlock)
          join.save
        end
      end
    end
  end
end

if ARGV[1] == "add"
  userpath = ARGV[2]

  if Profile.where(profile_path: userpath).count > 0
    raise "Profile " + userpath + " already exists"
  end

  profile = Profile.new(profile_path: userpath)
  profile.save

  scrape_profile profile, true
elsif ARGV[1] == "update"
  if ARGV.size == 3
    scrape_profile Profile.where(profile_path: ARGV[2]).first, false
  else
    Profile.all.each do |profile|
      scrape_profile profile, false
    end
  end
elsif ARGV[1] == "full"
  if ARGV.size == 3
    scrape_profile Profile.where(profile_path: ARGV[2]).first, true
  else
    Profile.all.each do |profile|
      scrape_profile profile, true
    end
  end
elsif ARGV[1] == "recolor"
  Game.all.each do |game|
    moon_index = Random.rand(MOON_COLORS.size)

    game.color = MOON_COLORS[moon_index]
    game.save
  end
end