summaryrefslogtreecommitdiffstats
path: root/src/fetch/scripts/boardgamegeek.rb
blob: b3cf4f36032da9a24ad089a5d1f55fd3c474d3e0 (plain)
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#!/usr/bin/env ruby
#
# ***************************************************************************
#    copyright            : (C) 2006 by Steve Beattie
#                         : (C) 2008 by Sven Werlen
#    email                : [email protected]
#                         : [email protected]
# ***************************************************************************
#
# ***************************************************************************
# *                                                                         *
# *   This program is free software; you can redistribute it and/or modify  *
# *   it under the terms of version 2 of the GNU General Public License as  *
# *   published by the Free Software Foundation;                            *
# *                                                                         *
# ***************************************************************************

# $Id: boardgamegeek.rb 313 2006-10-02 22:17:11Z steve $

# This program is expected to be invoked from tellico
# (http://periapsis.org/tellico) as an external data source. It provides
# searches for boardgames from the boardgamegeek.com website, via
# boardgamegeek's xmlapi interface
# (http://www.boardgamegeek.com/xmlapi/)
#
# It only allows searches via name; the boardgamegeek xmlapi is not yet
# rich enough to support queries by designer, publisher, category, or
# mechanism. I'd like to add support for querying by boardgamegeek id,
# but that needs additional support in tellico.
#
# Sven Werlen: 03 Feb 2008: script has been extended to retrieve cover
# images (/thumbnail from xmlapi). Images are retrieved from the website
# and base64 is generated on-the-fly.
#
require 'rexml/document'
require 'net/http'
require 'cgi'
require "base64"
include REXML

$my_version = '$Rev: 313 $'

class Game
  attr_writer :year
  attr_writer :description
  attr_writer :cover
  attr_writer :image

  def initialize(name, id)
    @name = name
    @id = id
    @publishers = []
    @designers = []
    @players = []
  end

  def add_publisher(publisher)
    @publishers << publisher
  end

  def add_designer(designer)
    @designers << designer
  end

  def add_players(players)
    @players << players
  end

  def to_s()
    "@name (#@id #@publishers #@year)"
  end

  def toXML()
    element = Element.new 'entry'
    element.add_element Element.new('title').add_text(@name)
    element.add_element Element.new('description').add_text(@description) if @description
    element.add_element Element.new('year').add_text(@year) if @year
    element.add_element Element.new('boardgamegeek-link').add_text("http://www.boardgamegeek/game/#{@id}") if @id
    element.add_element Element.new('bggid').add_text(@id) if @id
    element.add_element Element.new('cover').add_text(@cover) if @cover
  
    if @publishers.length > 0
      pub_elements = Element.new('publishers')
      @publishers.each {|p| pub_elements.add_element Element.new('publisher').add_text(p)}
      element.add_element pub_elements
    end
    if @designers.length > 0
      des_elements = Element.new('designers')
      @designers.each {|d| des_elements.add_element Element.new('designer').add_text(d)}
      element.add_element des_elements
    end
    if @players.length > 0
      players_elements = Element.new('num-players')
      @players.each {|n| players_elements.add_element Element.new('num-player').add_text(n.to_s)}
      element.add_element players_elements
    end
    return element
  end
  
  def image()
    image = Element.new 'image'
    image.add_attribute('format', 'JPEG')
    image.add_attribute('id', @id + ".jpg")
    image.add_text(@image)
    return image
  end
end

def getGameList(query)
  #puts("Query is #{query}")

  search_result = nil
  Net::HTTP.start('www.boardgamegeek.com', 80) do
    |http| search_result = (http.get("/xmlapi/search?search=#{CGI.escape(query)}",
                                    {"User-Agent" => "BoardGameGeek plugin for Tellico #{$my_version}"}).body)
      http.finish
  end
  doc = REXML::Document.new(search_result)

  games = XPath.match(doc, "//game")
  #games.each {|g| puts g.elements['name'].text+g.attributes['gameid']}
  ids = []
  games.each {|g| ids << g.attributes['gameid']}
  return ids
end

def getGameDetails(ids)
  #ids.each {|id| puts id}

  query = "/xmlapi/game/#{ids.join(',')}"
  #puts query
  search_result = nil
  Net::HTTP.start('www.boardgamegeek.com', 80) do |http|
    search_result = http.get(query, {"User-Agent" => "BoardGameGeek plugin for Tellico #{$my_version}"})
    http.finish
  end
  games = []
  case search_result
  when Net::HTTPOK then
    doc = REXML::Document.new(search_result.body)

    games_xml = XPath.match(doc, "//game")
    games_xml.each do |g| 
      if( g.elements['name'] != nil )
        game = Game.new(g.elements['name'].text, g.attributes['gameid'])
        game.year = g.elements['yearpublished'].text
        game.description = g.elements['description'].text
        g.elements.each('publisher'){|p| game.add_publisher p.elements['name'].text}
        g.elements.each('designer'){|d| game.add_designer d.elements['name'].text}
        minp = Integer(g.elements['minplayers'].text)
        maxp = Integer(g.elements['maxplayers'].text)
        minp.upto(maxp) {|n| game.add_players(n)}
        
        # retrieve cover
        coverurl = g.elements['thumbnail'] != nil ? g.elements['thumbnail'].text : nil
        if( coverurl =~ /files.boardgamegeek.com(.*)$/ )
          # puts "downloading... " + $1
          cover = nil
          Net::HTTP.start('files.boardgamegeek.com', 80) do |http| 
            cover = (http.get($1, {"User-Agent" => "BoardGameGeek plugin for Tellico #{$my_version}"}))
          end
          case cover
          when Net::HTTPOK then
            game.cover = g.attributes['gameid'] + ".jpg";
            game.image = Base64.encode64(cover.body);
          end
        else
          # puts "invalid cover: " + coverurl
        end
        games << game
      end
    end
  end
  return games
end

def listToXML(gameList)
  doc = REXML::Document.new
  doc << REXML::DocType.new('tellico PUBLIC', '"-//Robby Stephenson/DTD Tellico V10.0//EN" "http://periapsis.org/tellico/dtd/v10/tellico.dtd"')
  doc << XMLDecl.new
  tellico = Element.new 'tellico'
  tellico.add_attribute('xmlns', 'http://periapsis.org/tellico/')
  tellico.add_attribute('syntaxVersion', '10')
  collection = Element.new 'collection'
  collection.add_attribute('title', 'My Collection')
  collection.add_attribute('type', '13')

  fields = Element.new 'fields'
  field = Element.new 'field'
  field.add_attribute('name', '_default')
  fields.add_element(field)
  field = Element.new 'field'
  field.add_attribute('name', 'bggid')
  field.add_attribute('title', 'BoardGameGeek ID')
  field.add_attribute('category', 'General')
  field.add_attribute('flags', '0')
  field.add_attribute('format', '4')
  field.add_attribute('type', '6')
  field.add_attribute('i18n', 'true')
  fields.add_element(field)
  collection.add_element(fields)

  images = Element.new 'images'

  id = 0
  gameList.each do
    |g| element = g.toXML()
        element.add_attribute('id', id)
        id = id + 1
      collection.add_element(element)
      images.add_element(g.image());
  end
  collection.add_element(images);  
  tellico.add_element(collection)
  doc.add_element(tellico)
  doc.write($stdout, 0)
  puts ""
end

if __FILE__ == $0

  def showUsage
    warn "usage: #{__FILE__} game_query"
    exit 1
  end

  showUsage unless ARGV.length == 1

  idList = getGameList(ARGV.shift)
  if idList
    gameList = getGameDetails(idList)
  end

  listToXML(gameList)
end