class FFXIV::Lodestone::Character

Attributes

birthday[RW]
bpd[RW]
city_state[RW]
classes[RW]
data_center[RW]
end_contents[RW]
end_contents?[RW]
eternal_bonding[RW]
eternal_bonding?[RW]
first_blogged[RW]
free_company[RW]
free_company_rank[RW]
gender[RW]
grand_company[RW]
grand_company_rank[RW]
guardian[RW]
id[RW]
image_uri[RW]
latest_blogged[RW]
linkshell_rank[RW]
minions[RW]
mounts[RW]
name[RW]
nameday[RW]
num_blogs[RW]
race[RW]
self_introduction[RW]
server[RW]
subrace[RW]
thumbnail_uri[RW]

Public Class Methods

find_by_id(id) click to toggle source
# 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[:data_center] = Utils.data_center(props[:server])
    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
            props[:birthday] = Utils.ed2gd(props[:nameday])
          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 |cls, discipline|
      props[:classes][discipline] = {}
      dom.search("h4.class_#{cls} + 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) click to toggle source
# 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

Public Instance Methods

blogs(load_details = false) click to toggle source
# File lib/ffxiv/lodestone/character.rb, line 177
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

Private Instance Methods

init_blog() click to toggle source
# File lib/ffxiv/lodestone/character.rb, line 258
def init_blog
  if @num_blogs.nil?
    dom1 = Lodestone.fetch("character/#{@id}/blog?order=1")
    total_node = dom1.at("div.current_list span.total")
    @num_blogs = total_node.nil? ? 0 : total_node.content.to_i
    if @num_blogs > 0
      dom2 = Lodestone.fetch("character/#{@id}/blog?order=2")
      {latest_blogged: dom1, first_blogged: dom2}.each do |prop, dom|
        txt = dom.at("h3.header_title").content
        uts = txt.match(/ldst_strftime\((\d{10}), 'YMDHM'\)/)[1].to_i
        send("#{prop}=", Time.at(uts).utc)
      end
      @bpd = (@num_blogs / ((Time.now - @first_blogged) / 86400.0)).round(2)
    end
  end
end