ffxiv 0.9.1
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.
- checksums.yaml +7 -0
- data/lib/ffxiv.rb +6 -0
- data/lib/ffxiv/lodestone.rb +24 -0
- data/lib/ffxiv/lodestone/character.rb +112 -0
- data/lib/ffxiv/lodestone/free-company.rb +116 -0
- data/lib/ffxiv/lodestone/model.rb +24 -0
- metadata +49 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 3fb7ce29800d85a1d741dced5bb8ba1049b63115
|
|
4
|
+
data.tar.gz: 40595334045f34238ec0e636e896fb24f521252f
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: cdfe1416572fa6daf7a401c00d48a3d87720777b531585230f7d82a0c5e539f4984e8be5f365c8f599ab384a59261245a51132256a04022a7b051391242f8f23
|
|
7
|
+
data.tar.gz: a5951abe15687d137cee233c7fd2fda8a224d73cc5d016caf7b5688e0baaeb91f1d1d9f3baaa4bdadde382b8f39d51f055321567cdac4ae861ed10454a8f1e22
|
data/lib/ffxiv.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
|
2
|
+
|
|
3
|
+
require "pp"
|
|
4
|
+
require "nokogiri"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "open-uri"
|
|
7
|
+
|
|
8
|
+
require "lodestone/model"
|
|
9
|
+
require "lodestone/character"
|
|
10
|
+
require "lodestone/free-company"
|
|
11
|
+
|
|
12
|
+
module FFXIV
|
|
13
|
+
module Lodestone
|
|
14
|
+
class << self
|
|
15
|
+
def fetch(endpoint)
|
|
16
|
+
uri = "http://na.finalfantasyxiv.com/lodestone/" + endpoint
|
|
17
|
+
html, charset = open(uri) do |page|
|
|
18
|
+
[page.read, page.charset]
|
|
19
|
+
end
|
|
20
|
+
Nokogiri::HTML.parse(html, nil, charset)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module FFXIV
|
|
2
|
+
module Lodestone
|
|
3
|
+
class Character < Model
|
|
4
|
+
|
|
5
|
+
attr_accessor :id, :name, :server, :thumbnail_uri, :image_uri, :race, :subrace, :gender,
|
|
6
|
+
:nameday, :guardian, :city_state, :grand_company, :grand_company_rank,
|
|
7
|
+
:free_company, :minions, :mounts, :end_contents, :self_introduction, :classes,
|
|
8
|
+
:num_blogs, :first_blogged, :latest_blogged, :bpd, :free_company_rank
|
|
9
|
+
alias :end_contents? :end_contents
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
|
|
13
|
+
def name_to_id(name, server)
|
|
14
|
+
dom = Lodestone.fetch("character/?q=#{URI.escape(name)}&worldname=#{URI.escape(server)}")
|
|
15
|
+
dom.at("h4.player_name_gold a").attr("href").split("/")[-1].to_i
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def find_by_id(id)
|
|
19
|
+
begin
|
|
20
|
+
dom = Lodestone.fetch("character/#{id}")
|
|
21
|
+
|
|
22
|
+
props = {}
|
|
23
|
+
props[:id] = id
|
|
24
|
+
props[:name] = dom.css("div.player_name_txt h2 a").inner_text
|
|
25
|
+
props[:server] = dom.css("div.player_name_txt h2 span").inner_text[2...-1]
|
|
26
|
+
props[:thumbnail_uri] = drop_uts(dom.css("div.player_name_thumb img").attr("src").inner_text)
|
|
27
|
+
props[:image_uri] = drop_uts(dom.css("img[width='264']").attr("src").inner_text)
|
|
28
|
+
props[:race], props[:subrace], props[:gender] = dom.css("div.chara_profile_title").inner_text.strip.split(" / ")
|
|
29
|
+
dom.css("ul.chara_profile_list li").to_a.each do |n|
|
|
30
|
+
t = n.inner_text
|
|
31
|
+
if t.include?("Nameday")
|
|
32
|
+
props[:nameday], props[:guardian] = n.css(".txt_yellow").to_a.map(&:inner_text)
|
|
33
|
+
elsif t.include?("City-state")
|
|
34
|
+
props[:city_state] = n.css(".txt_yellow").inner_text
|
|
35
|
+
elsif t.include?("Grand Company")
|
|
36
|
+
props[:grand_company], props[:grand_company_rank] = n.css(".txt_yellow").inner_text.split("/")
|
|
37
|
+
elsif t.include?("Free Company")
|
|
38
|
+
props[:free_company] = n.css(".txt_yellow").inner_text
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
dom.css("div.area_header_w358_inner").to_a.each do |n|
|
|
42
|
+
aname = n.css("h4.ic_silver").inner_text.downcase
|
|
43
|
+
items = n.css("a.ic_reflection_box").to_a.map{|m| m.attr("title")}.map{|i| i.capitalize.gsub(/[\s][a-z]/) {|s| s.upcase}}.sort
|
|
44
|
+
props[:"#{aname}"] = items
|
|
45
|
+
end
|
|
46
|
+
props[:end_contents] = props[:mounts].include?("Magitek Armor")
|
|
47
|
+
props[:self_introduction] = dom.css("div.txt_selfintroduction").inner_html.strip
|
|
48
|
+
props[:classes] = {}
|
|
49
|
+
%w{fighter sorcerer crafter gatherer}.each do |discipline|
|
|
50
|
+
dom.css("h4.class_#{discipline} + div.table_black_w626 td.ic_class_wh24_box").to_a.each do |td|
|
|
51
|
+
txt = td.inner_text
|
|
52
|
+
if !txt.empty?
|
|
53
|
+
lvl = td.next_sibling().next_sibling().inner_text.to_i
|
|
54
|
+
props[:classes][txt] = lvl == 0 ? nil : lvl
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
self.new(props)
|
|
60
|
+
rescue => e
|
|
61
|
+
pp e
|
|
62
|
+
nil
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def num_blogs
|
|
69
|
+
init_blog; @num_blogs
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def bpd
|
|
73
|
+
init_blog; @bpd
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def latest_blogged
|
|
77
|
+
init_blog; @latest_blogged
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def first_blogged
|
|
81
|
+
init_blog; @first_blogged
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def thumbnail_uri
|
|
85
|
+
@thumbnail_uri + "?#{Time.now.to_i}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def image_uri
|
|
89
|
+
@image_uri + "?#{Time.now.to_i}"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
def init_blog
|
|
94
|
+
if @num_blogs.nil?
|
|
95
|
+
dom1 = Lodestone.fetch("character/#{@id}/blog?order=1")
|
|
96
|
+
total_node = dom1.at("div.current_list span.total")
|
|
97
|
+
@num_blogs = total_node.nil? ? 0 : total_node.inner_text.to_i
|
|
98
|
+
if @num_blogs > 0
|
|
99
|
+
dom2 = Lodestone.fetch("character/#{@id}/blog?order=2")
|
|
100
|
+
{latest_blogged: dom1, first_blogged: dom2}.each do |prop, dom|
|
|
101
|
+
txt = dom.at("h3.header_title").inner_text
|
|
102
|
+
uts = txt.match(/ldst_strftime\((\d{10}), 'YMDHM'\)/)[1].to_i
|
|
103
|
+
send("#{prop}=", Time.at(uts).utc)
|
|
104
|
+
end
|
|
105
|
+
@bpd = (@num_blogs / ((Time.now - @first_blogged) / 86400.0)).round(2)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
module FFXIV
|
|
2
|
+
module Lodestone
|
|
3
|
+
class FreeCompany < Model
|
|
4
|
+
|
|
5
|
+
attr_accessor :id, :name, :server, :tag, :logo_uri, :formed, :members, :num_members, :rank,
|
|
6
|
+
:grand_company, :grand_company_standing, :slogan, :focus, :seeking, :active,
|
|
7
|
+
:recruiting, :housing, :weekly_rank, :monthly_rank
|
|
8
|
+
alias :recruiting? :recruiting
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
|
|
12
|
+
def name_to_id(name, server)
|
|
13
|
+
dom = Lodestone.fetch("freecompany/?q=#{URI.escape(name)}&worldname=#{URI.escape(server)}")
|
|
14
|
+
dom.at("div.ic_freecompany_box a").attr("href").split("/")[-1].to_i
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def find_by_id(id)
|
|
18
|
+
begin
|
|
19
|
+
dom = Lodestone.fetch("freecompany/#{id}")
|
|
20
|
+
|
|
21
|
+
props = {}
|
|
22
|
+
props[:id] = id
|
|
23
|
+
|
|
24
|
+
props[:logo_uri] = dom.css("div.ic_crest_64 img").to_a.map{|n| n.attr("src")}
|
|
25
|
+
|
|
26
|
+
gcs_node = dom.at("span.friendship_color")
|
|
27
|
+
props[:grand_company_standing] = gcs_node.inner_text[1...-1]
|
|
28
|
+
|
|
29
|
+
gc_node = gcs_node.previous_sibling
|
|
30
|
+
props[:grand_company] = gc_node.inner_text.strip
|
|
31
|
+
|
|
32
|
+
name_node = gcs_node.next_sibling.next_sibling.next_sibling
|
|
33
|
+
props[:name] = name_node.inner_text
|
|
34
|
+
|
|
35
|
+
server_node = name_node.next_sibling.next_sibling
|
|
36
|
+
props[:server] = server_node.inner_text[1...-1]
|
|
37
|
+
|
|
38
|
+
props[:tag] = dom.at("td.vm").last_element_child.next_sibling.inner_text[1...-1]
|
|
39
|
+
|
|
40
|
+
dom.css("table.table_style2 tr").to_a.each do |tr|
|
|
41
|
+
td_node = tr.at("td")
|
|
42
|
+
td_text = td_node.inner_text.strip
|
|
43
|
+
case tr.at("th").inner_text
|
|
44
|
+
when "Formed"
|
|
45
|
+
props[:formed] = Time.at(td_text.match(/ldst_strftime\((\d{10}), 'YMD'\)/)[1].to_i).utc
|
|
46
|
+
when "Rank"
|
|
47
|
+
props[:rank] = td_text.to_i
|
|
48
|
+
when "Active Members"
|
|
49
|
+
props[:num_members] = td_text.to_i
|
|
50
|
+
when "Ranking"
|
|
51
|
+
weekly, monthly, __garbage = td_node.inner_html.split("<br>").map(&:strip)
|
|
52
|
+
props[:weekly_rank] = weekly.include?("--") ? nil : weekly.match(/\d+/)[0].to_i
|
|
53
|
+
props[:monthly_rank] = monthly.include?("--") ? nil : monthly.match(/\d+/)[0].to_i
|
|
54
|
+
when "Company Slogan"
|
|
55
|
+
props[:slogan] = td_text
|
|
56
|
+
when "Active"
|
|
57
|
+
props[:active] = td_text
|
|
58
|
+
when "Recruitment"
|
|
59
|
+
props[:recruiting] = td_text == "Open"
|
|
60
|
+
when "Focus"
|
|
61
|
+
props[:focus] = td_node.css("img").to_a.map{|n| n.attr("title")}
|
|
62
|
+
when "Seeking"
|
|
63
|
+
props[:seeking] = td_node.css("img").to_a.map{|n| n.attr("title")}
|
|
64
|
+
when "Estate Profile"
|
|
65
|
+
name_node = td_node.at("div.txt_yellow")
|
|
66
|
+
if name_node
|
|
67
|
+
address_size, greeting = td_node.css("p").to_a.map(&:inner_text)
|
|
68
|
+
address, size = address_size.split(" (")
|
|
69
|
+
props[:housing] = {
|
|
70
|
+
name: name_node.inner_text,
|
|
71
|
+
address: address,
|
|
72
|
+
size: size[0...-1],
|
|
73
|
+
greeting: greeting
|
|
74
|
+
}
|
|
75
|
+
else
|
|
76
|
+
props[:housing] = nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
self.new(props)
|
|
81
|
+
rescue => e
|
|
82
|
+
pp e
|
|
83
|
+
nil
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def members
|
|
89
|
+
if @members.nil?
|
|
90
|
+
members = {}
|
|
91
|
+
num_pages = (@num_members / 50.0).ceil # 50 members / page
|
|
92
|
+
1.upto(num_pages) do |page_no|
|
|
93
|
+
dom = Lodestone.fetch("freecompany/#{@id}/member/?page=#{page_no}")
|
|
94
|
+
dom.css("div.player_name_area").to_a.each do |node|
|
|
95
|
+
cid = node.at("div.name_box a").attr("href").split("/")[-1]
|
|
96
|
+
fcrank = node.at("div.fc_member_status").inner_text
|
|
97
|
+
members[cid] = fcrank
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
characters = []
|
|
101
|
+
members.each do |cid, fcrank|
|
|
102
|
+
character = Character.find(cid)
|
|
103
|
+
character.free_company_rank = fcrank.strip
|
|
104
|
+
characters << character
|
|
105
|
+
end
|
|
106
|
+
@members = characters
|
|
107
|
+
end
|
|
108
|
+
@members
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module FFXIV
|
|
2
|
+
module Lodestone
|
|
3
|
+
class Model
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
def find_by_name(name, server)
|
|
7
|
+
self.find_by_id(self.name_to_id(name, server))
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(props = {})
|
|
12
|
+
props.each do |name, value|
|
|
13
|
+
self.send("#{name}=", value) if self.respond_to?(name)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
def self.drop_uts(str)
|
|
19
|
+
str[0...(str.size - 11)] # ?1234567890
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ffxiv
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.9.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Isjaki Kveikur
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2014-09-29 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: An unofficial FFXIV ARR toolkit for Ruby, featuring Lodestone scraper.
|
|
14
|
+
email:
|
|
15
|
+
- isjaki.xiv@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- lib/ffxiv.rb
|
|
21
|
+
- lib/ffxiv/lodestone.rb
|
|
22
|
+
- lib/ffxiv/lodestone/character.rb
|
|
23
|
+
- lib/ffxiv/lodestone/free-company.rb
|
|
24
|
+
- lib/ffxiv/lodestone/model.rb
|
|
25
|
+
homepage: https://github.com/ffxiv/ffxiv
|
|
26
|
+
licenses:
|
|
27
|
+
- MIT
|
|
28
|
+
metadata: {}
|
|
29
|
+
post_install_message:
|
|
30
|
+
rdoc_options: []
|
|
31
|
+
require_paths:
|
|
32
|
+
- lib
|
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
34
|
+
requirements:
|
|
35
|
+
- - ">="
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '0'
|
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '0'
|
|
43
|
+
requirements: []
|
|
44
|
+
rubyforge_project:
|
|
45
|
+
rubygems_version: 2.2.2
|
|
46
|
+
signing_key:
|
|
47
|
+
specification_version: 4
|
|
48
|
+
summary: An unofficial FFXIV ARR toolkit for Ruby, featuring Lodestone scraper.
|
|
49
|
+
test_files: []
|