Class: FFXIV::Lodestone::Character

Inherits:
Model
  • Object
show all
Defined in:
lib/ffxiv/lodestone/character.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Model

#attributes, find_by_name, #initialize

Constructor Details

This class inherits a constructor from FFXIV::Lodestone::Model

Instance Attribute Details

#birthdayObject

Returns the value of attribute birthday.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def birthday
  @birthday
end

#bpdObject

Returns the value of attribute bpd.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def bpd
  @bpd
end

#city_stateObject

Returns the value of attribute city_state.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def city_state
  @city_state
end

#classesObject

Returns the value of attribute classes.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def classes
  @classes
end

#data_centerObject

Returns the value of attribute data_center.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def data_center
  @data_center
end

#end_contentsObject Also known as: end_contents?

Returns the value of attribute end_contents.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def end_contents
  @end_contents
end

#eternal_bondingObject Also known as: eternal_bonding?

Returns the value of attribute eternal_bonding.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def eternal_bonding
  @eternal_bonding
end

#first_bloggedObject

Returns the value of attribute first_blogged.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def first_blogged
  @first_blogged
end

#free_company(fetch = false) ⇒ Object

Returns the value of attribute free_company.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def free_company
  @free_company
end

#free_company_rankObject

Returns the value of attribute free_company_rank.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def free_company_rank
  @free_company_rank
end

#genderObject

Returns the value of attribute gender.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def gender
  @gender
end

#grand_companyObject

Returns the value of attribute grand_company.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def grand_company
  @grand_company
end

#grand_company_rankObject

Returns the value of attribute grand_company_rank.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def grand_company_rank
  @grand_company_rank
end

#guardianObject

Returns the value of attribute guardian.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def guardian
  @guardian
end

#idObject

Returns the value of attribute id.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def id
  @id
end

#image_uriObject

Returns the value of attribute image_uri.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def image_uri
  @image_uri
end

#latest_bloggedObject

Returns the value of attribute latest_blogged.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def latest_blogged
  @latest_blogged
end

#linkshell_rankObject

Returns the value of attribute linkshell_rank.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def linkshell_rank
  @linkshell_rank
end

#minionsObject

Returns the value of attribute minions.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def minions
  @minions
end

#mountsObject

Returns the value of attribute mounts.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def mounts
  @mounts
end

#nameObject

Returns the value of attribute name.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def name
  @name
end

#namedayObject

Returns the value of attribute nameday.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def nameday
  @nameday
end

#num_blogsObject

Returns the value of attribute num_blogs.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def num_blogs
  @num_blogs
end

#raceObject

Returns the value of attribute race.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def race
  @race
end

#self_introductionObject

Returns the value of attribute self_introduction.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def self_introduction
  @self_introduction
end

#serverObject

Returns the value of attribute server.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def server
  @server
end

#subraceObject

Returns the value of attribute subrace.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def subrace
  @subrace
end

#thumbnail_uriObject

Returns the value of attribute thumbnail_uri.



5
6
7
# File 'lib/ffxiv/lodestone/character.rb', line 5

def thumbnail_uri
  @thumbnail_uri
end

Class Method Details

.find_by_id(id) ⇒ Object



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
# File 'lib/ffxiv/lodestone/character.rb', line 57

def find_by_id(id)
  begin
    dom = Lodestone.fetch("character/#{id}")

    props = {}
    props[:id] = id
    ch_name = dom.at("div.player_name_txt h2 a")
    props[:name] = ch_name.content
    props[:server] = ch_name.next_element.content.strip[1...-1]
    props[:thumbnail_uri] = drop_uts(dom.at("div.player_name_thumb img").attr("src"))
    props[:image_uri] = drop_uts(dom.at("div.bg_chara_264 img").attr("src"))
    props[:race], props[:subrace], gender = dom.at("div.chara_profile_title").content.strip.split(" / ")
    props[:gender] = case gender
      when "" then :female
      when "" then :male
      else raise "Unrecognized gender symbol: #{gender}"
    end

    months = {
      "1st Astral Moon" => 1,
      "1st Umbral Moon" => 2,
      "2nd Astral Moon" => 3,
      "2nd Umbral Moon" => 4,
      "3rd Astral Moon" => 5,
      "3rd Umbral Moon" => 6,
      "4th Astral Moon" => 7,
      "4th Umbral Moon" => 8,
      "5th Astral Moon" => 9,
      "5th Umbral Moon" => 10,
      "6th Astral Moon" => 11,
      "6th Umbral Moon" => 12
    }

    dom.search("dl.chara_profile_box_info").each do |n|
      n.search("dd.txt").each do |dd|
        dd_txt_name = dd.next_element
        t = dd_txt_name.content
        case dd.content
          when "Nameday"
            props[:nameday] = t
            match = t.match /^(\d+).+?the\s(.*)$/
            pp match
            props[:birthday] = Date.new(2013, months[match[2]], match[1].to_i)
          when "Guardian"
            props[:guardian] = t
          when "City-state"
            props[:city_state] = t
          when "Grand Company"
            props[:grand_company], props[:grand_company_rank] = t.split("/")
          when "Free Company"
            props[:free_company] = FreeCompany.new(id: dd_txt_name.at("a").attr("href").split("/")[-1].to_i, name: t, server: props[:server])
        end
      end
    end

    # The first "minion_box" contains mounts, and they need capitalization unlike minions.
    minion_boxes = dom.search("div.minion_box")
    props[:mounts] = minion_boxes[0].search("a").map{|a| a.attr("title").split.map(&:capitalize).join(' ')}
    props[:minions] = minion_boxes[1].search("a").map{|a| a.attr("title")}

    # Let's assume that whoever has this mount has slained Ultima Weapon and watched the ending movie, hence is qualified for the endgame contents.
    props[:end_contents] = props[:mounts].include?("Magitek Armor")

    # Likewise, assume that whoever has this mount has purchased Gold or Platinum eternal bonding.
    # Note that Standard version doesn't come with the mount, so use nil instead of false to indicate "unknown".
    props[:eternal_bonding] = props[:mounts].include?("Ceremony Chocobo") ? true : nil

    self_introduction = dom.at("div.txt_selfintroduction").inner_html.strip
    props[:self_introduction] = self_introduction == "" ? nil : self_introduction

    props[:classes] = {}
    disciplines = {
      "fighter" => "Figther",
      "sorcerer" => "Sorcerer",
      "extrajob" => "Extra Job",
      "crafter" => "Crafter",
      "gatherer" => "Gatherer"
    }
    disciplines.each do |discipline|
      props[:classes][discipline] = {}
      dom.search("h4.class_#{discipline.downcase} + div.table_black_w626 td.ic_class_wh24_box").each do |td|
        txt = td.content
        unless txt.empty?
          lvl = td.next_sibling().next_sibling().content.to_i
          props[:classes][discipline][txt] = lvl == 0 ? nil : lvl
        end
      end
    end

    self.new(props)
  rescue => e
    pp e
    nil
  end
end

.name_to_id(name, server) ⇒ Object



47
48
49
50
51
52
53
54
55
# File 'lib/ffxiv/lodestone/character.rb', line 47

def name_to_id(name, server)
  search_result = self.search(name, server: server)
  if search_result
    search_result[:characters].each do |ch|
      return ch.id if name.downcase == ch.name.downcase
    end
  end
  nil
end

.search(keyword, server: nil, page: 1, verbose: false) ⇒ Object



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
# File 'lib/ffxiv/lodestone/character.rb', line 14

def search(keyword, server: nil, page: 1, verbose: false)
  dom = Lodestone.fetch("character/?q=#{URI.escape(keyword)}&worldname=#{server ? URI.escape(server) : ""}&page=#{URI.escape(page.to_s)}")
  # Couldn't find a way to get this node with CSS...
  characters = dom.xpath("//comment()[.=' result ']/following::table[1]/tr").map do |tr|
    h4 = tr.at("td h4")
    a = h4.at("a")
    id = a.attr("href").split("/")[-1].to_i
    if verbose
      self.find_by_id(id)
    else
      self.new({
        id: id,
        name: a.content,
        server: h4.at("span").content.strip[1...-1],
        thumbnail_uri: drop_uts(tr.at("th img").attr("src"))
      })
    end
  end
  pagination = dom.at("div.current_list")
  if pagination
    span_total = pagination.at("span.total")
    raise "Character not found" unless span_total
    total = span_total.content.to_i
    results = {
      show_start: pagination.at("span.show_start").content.to_i,
      show_end: pagination.at("span.show_end").content.to_i,
      total: total,
      num_pages: (total / 50.0).ceil,
      characters: characters
    }
  end
end

Instance Method Details

#blogs(load_details = false) ⇒ Object



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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/ffxiv/lodestone/character.rb', line 186

def blogs(load_details = false)
  unless @blogs
    @blogs = []
    if num_blogs > 0
      num_blog_pages = (num_blogs / 10.0).ceil
      1.upto(num_blog_pages) do |page|
        dom = Lodestone.fetch("character/#{@id}/blog?order=2&page=#{page}")
        dom.search("section.base_body").each do |section_blog|
          a_title = section_blog.at("a.blog_title")
          blog = {
            id: a_title.attr("href").split("/")[-1],
            title: a_title.content,
            date: Time.at(section_blog.at("script").content[/ldst_strftime\((\d{10}),/, 1].to_i),
            num_comments: section_blog.at("span.ic_comment").content.to_i
          }
          if load_details
            dom_blog = Lodestone.fetch("character/#{@id}/blog/#{blog[:id]}")
            blog[:body] = dom_blog.at(".txt_selfintroduction").inner_html.strip

            blog[:comments] = []
            if blog[:num_comments] > 0
              dom_blog.search("div.comment").each do |dom_comment|
                unless dom_comment.at("div.comment_delete_box")
                  div_by = dom_comment.at("div.player_id")
                  by = nil
                  if div_by
                    a_by = div_by.at("a")
                    by = self.class.new({
                      id: a_by.attr("href").split("/")[-1],
                      name: a_by.content,
                      server: div_by.at("span").content.strip[1...-1]
                    })
                  end
                  blog[:comments] << {
                    id: dom_comment.previous_element.attr("name").split("_")[1].to_i,
                    by: by,
                    date: Time.at(dom_comment.at("script").content[/ldst_strftime\((\d{10}),/, 1].to_i),
                    body: dom_comment.at("div.balloon_body_inner").inner_html.strip
                  }
                end
              end
            end

            blog[:tags] = []
            a_tags = dom_blog.search("div.diary_tag a")
            if a_tags
              a_tags.each do |a_tag|
                blog[:tags] << a_tag.content[1...-1]
              end
            end

            blog[:images] = []
            img_thumbs = dom_blog.search("ul.thumb_list li img")
            if img_thumbs
              img_thumbs.each do |img_thumb|
                unless img_thumb.attr("class") == "img_delete"
                  blog[:images] << {
                    thumbnail: img_thumb.attr("src"),
                    original: img_thumb.attr("data-origin_src")
                  }
                end
              end
            end
          end
          @blogs << blog
        end
      end
    end
  end
  @blogs
end