xiv_lodestone 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7a432c0ad6b311ba278eb7f9dd6b955f7e871a3e
4
+ data.tar.gz: 57ce133ac2807b8f95bb33e728753ccdb0541e21
5
+ SHA512:
6
+ metadata.gz: ddd77ff25fdc421423e8f47447e1e456adbb08cf0462c21cb74fe104b590aef64934d81b274fa544a919ef644ccc59632570fb05c7240aa691adb5ab3740da0b
7
+ data.tar.gz: acd1e309345ec4d6555b09771efafe865c510926a80bd355751e833e96d3939747bef716e8a4676178a35f81def9b23c374281b6a11b19ad03ef14f520d405d8
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
4
+ sudo: false
5
+ addons:
6
+ code_climate:
7
+ repo_token:
8
+ secure: "cNY3+gHTFC/mL3K34u+JR4UvsvREtytTUakNjy/J6avX1Zrgbzo26p4JzKNIsDzDvPkkaQqhLkdELMBnNjermTFFGtbJ8PNU1rNWwsLJ/R+RsFzrD8eBOgCBgLVt798qz7skLzKpiUzoU8rEq5ksyn1sBHo6OcrrKwvE8lQY8zs="
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'nokogiri'
6
+ gem 'json'
7
+
8
+ group :test do
9
+ gem 'rake'
10
+ gem 'codeclimate-test-reporter', require: nil
11
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Benjamin Evenson
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # XIVLodestone - A simple FFXIV lodestone scraper [![Build Status](https://travis-ci.org/benjiro/XIV-lodestone.svg)](https://travis-ci.org/benjiro/XIV-lodestone) [![Code Climate](https://codeclimate.com/github/benjiro/XIV-lodestone/badges/gpa.svg)](https://codeclimate.com/github/benjiro/XIV-lodestone) [![Test Coverage](https://codeclimate.com/github/benjiro/XIV-lodestone/badges/coverage.svg)](https://codeclimate.com/github/benjiro/XIV-lodestone)
2
+
3
+ A simple API for scraping information from FFXIV lodestone community website.
4
+
5
+ ## Features
6
+ - Character Search (via ID number or Name and Server)
7
+ - Character profile
8
+ - HP, MP, TP
9
+ - Race, Sex, Clan, Nameday, Guardian, City, Grand Company, Free Company
10
+ - Gear List - Shows item name, ilevel, slot, ffxiv db url, calculates total ilevel
11
+ - Disciple List(Classes) - Shows class name, level, current exp, total exp, icon url, calculates experience to next level
12
+ - All character attributes
13
+
14
+ ## TODO
15
+ - Profile picture
16
+ - Achievements, Blogs, Friends list
17
+ - Free Company Search/Gather Member information
18
+ - Clean up smelly code 24/7
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ ```ruby
25
+ gem 'xiv_lodestone'
26
+ ```
27
+
28
+ And then execute:
29
+
30
+ $ bundle
31
+
32
+ Or install it yourself as:
33
+
34
+ $ gem install xiv_lodestone
35
+
36
+ ## Usage
37
+
38
+ TODO: Write a more indepth guide
39
+
40
+ Once the gem is installed include it in your project.
41
+
42
+ ```ruby
43
+ require 'xiv_lodestone'
44
+ ```
45
+
46
+ To parse a character
47
+ ```ruby
48
+ $ character = XIVLodestone::Character("Benpi Kancho", "Tonberry")
49
+ $ character = XIVLodestone::Character(1549391)
50
+ ```
51
+
52
+ Basic examples
53
+
54
+ ```ruby
55
+ character.gear.weapon # => Hash of below data
56
+ character.gear.weapon.name # => weapon name #String
57
+ character.gear.weapon.ilevel # => item level Integer
58
+ character.gear.weapon.slot # => item slot #String
59
+ character.gear.weapon.url # => url to ffxivdb #String
60
+ character.gear.ilevel # => Character ilevel
61
+
62
+ character.disciple.rogue # => Hash of below data
63
+ character.disciple.rogue.name # => class name #String
64
+ character.disciple.rogue.level # => current level #Integer
65
+ character.disciple.rogue.current_exp # => current experience #Interger
66
+ character.disciple.rogue.total_exp # => total experience #Integer
67
+ character.disciple.rogue.icon_url # => class icon url #String
68
+ character.disciple.rogue.next_level # => experience to required to level #Integer
69
+
70
+ character.str # => Character strength value
71
+ # More to come
72
+ ```
73
+
74
+ ## Contributing
75
+
76
+ 1. Fork it ( https://github.com/[my-github-username]/xiv-lodestone/fork )
77
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
78
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
79
+ 4. Push to the branch (`git push origin my-new-feature`)
80
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
@@ -0,0 +1,114 @@
1
+ require 'xiv_lodestone/lodestone_helper'
2
+ require 'xiv_lodestone/lodestone_parser'
3
+
4
+ module XIVLodestone
5
+ # A Object that representation a FFXIV:ARR character,
6
+ # all information is obtained from the lodestone website.
7
+ class Character
8
+ def initialize(*args)
9
+ parser = nil
10
+ if args.count == 1 && args.all? {|x| x.is_a? Fixnum}
11
+ parser = Parser.new(Helper.open_id(args.first))
12
+ elsif args.count == 2 && args.all? {|x| x.is_a? String}
13
+ parser = Parser.new(Helper.open_url(args.at(0), args.at(1)))
14
+ else
15
+ fail ArgumentError, "Invalid Arguments: player_id(Fixnum) or player_name(String), server_name(String)]"
16
+ end
17
+
18
+ @profile = Hash.new
19
+ @profile[:disciple] = DiscipleList.new(parser.get_classes)
20
+ @profile[:gear] = GearList.new(parser.get_gear)
21
+ @profile.merge!(parser.get_attributes)
22
+ @profile[:hp] = parser.get_hp
23
+ @profile[:mp] = parser.get_mp
24
+ @profile[:tp] = parser.get_tp
25
+ @profile[:sex] = parser.get_sex
26
+ @profile[:race] = parser.get_race
27
+ @profile[:clan] = parser.get_clan
28
+ @profile[:nameday] = parser.get_nameday
29
+ @profile[:guardian] = parser.get_guardian
30
+ @profile[:city] = parser.get_city
31
+ @profile[:grand_company] = parser.get_grand_company
32
+ @profile[:free_company] = parser.get_free_company
33
+
34
+ parser = nil #Close the reference so Nokogiri cleans up itself
35
+ end
36
+
37
+ def method_missing(method)
38
+ return @profile[method] if @profile.key?(method)
39
+ super
40
+ end
41
+ end
42
+ # A Object that represents a list of Gear pieces
43
+ # The initialiser takes a hash of items in the following layout
44
+ # { :weapon => ["Fist", 110, "Weapon", "http://...."], ... }
45
+ class GearList
46
+ def initialize(gear_list)
47
+ @list = Hash.new
48
+ gear_list.each do |key, value|
49
+ gear = Gear.new(value[0], value[1], value[2], value[3])
50
+ @list[key] = gear
51
+ end
52
+ end
53
+ # Calculates the total gear list ilevel
54
+ # Rounds to the nearest whole number like FFXIV ingame calculation
55
+ # returns a #Integer
56
+ def ilevel()
57
+ ilevel = 0
58
+ @list.each_value do |value|
59
+ ilevel += value.ilevel
60
+ end
61
+ (@list.key?(:shield)) ? (ilevel/13).round : (ilevel/12).round
62
+ end
63
+ # Generates access methods for each item slot
64
+ def method_missing(method)
65
+ return @list[method] if @list.key?(method)
66
+ super
67
+ end
68
+ # A object representation of a peacie of gear.
69
+ # TODO Add more information
70
+ class Gear
71
+ attr_reader :name, :ilevel, :slot, :url
72
+
73
+ def initialize(name, ilevel, slot, url)
74
+ @name = name
75
+ @ilevel = ilevel
76
+ @slot = slot
77
+ @url = url
78
+ end
79
+ end
80
+ end
81
+ # A object representation of disciples(classes)
82
+ # The initialiser takes a hash of Disciple, that layout follows
83
+ # { :rogue => ["Rogue", 1, 0, 300, "http://..."] }
84
+ class DiscipleList
85
+ def initialize(disciple_list)
86
+ @list = Hash.new
87
+ disciple_list.each do |key, value|
88
+ disciple = Disciple.new(value[0], value[1], value[2], value[3], value[4])
89
+ @list[key.to_sym] = disciple
90
+ end
91
+ # Generates access methods for each disciple slot
92
+ def method_missing(method)
93
+ return @list[method] if @list.key?(method)
94
+ super
95
+ end
96
+ end
97
+ # A object representation of a disciple
98
+ class Disciple
99
+ attr_reader :name, :level, :current_exp, :total_exp, :icon_url
100
+
101
+ def initialize(name, level, curr, req, icon)
102
+ @name = name
103
+ @level = level
104
+ @current_exp = curr
105
+ @total_exp = req
106
+ @icon_url = icon
107
+ end
108
+ # Returns the required experience to the next level
109
+ def next_level()
110
+ @total_exp - @current_exp
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,50 @@
1
+ require 'nokogiri'
2
+ require 'open-uri'
3
+
4
+ module XIVLodestone
5
+ # RuntimeError if a character can't be found
6
+ class CharacterNotFound < RuntimeError
7
+ end
8
+ # RuntimeError if more than one character is found
9
+ class MoreThanOneCharacter < RuntimeError
10
+ end
11
+ # A class of helper methods
12
+ class Helper
13
+ # Find a character profile from a given name and
14
+ # server. Returns a Nokogiri XML document of the
15
+ # characters page.
16
+ def self.open_url(name, server)
17
+ page = Nokogiri::HTML(open_character_url(name, server))
18
+ check_valid_url(page)
19
+ id = page.xpath('//h4/a')[0]['href'].split(/\//).last
20
+ Nokogiri::HTML(open_id_url(id))
21
+ end
22
+ # Find a characters profiles from a given id.
23
+ # Returns a Nokogiri XML document of the characters
24
+ # page.
25
+ def self.open_id(id)
26
+ Nokogiri::HTML(open_id_url(id))
27
+ end
28
+ # Open a URL with the given name and server.
29
+ # Returns a file stream.
30
+ def self.open_character_url(name, server)
31
+ open('http://na.finalfantasyxiv.com/lodestone/character/' \
32
+ "?q=#{name}&worldname=#{server}")
33
+ end
34
+ # Open a URL with the given id.
35
+ # Reutrn a file stream.
36
+ def self.open_id_url(id)
37
+ open("http://na.finalfantasyxiv.com/lodestone/character/#{id}")
38
+ end
39
+ # Validates a Nokogiri document for a character.
40
+ # Throws a CharacterNotFound exception.
41
+ # Throws a MoreThanOneCharacter exception.
42
+ def self.check_valid_url(page)
43
+ fail CharacterNotFound unless page.at_xpath('//h4/a')
44
+ fail MoreThanOneCharacter if page.xpath('//h4/a').size > 1
45
+ end
46
+
47
+ private_class_method :open_character_url, :open_id_url
48
+ private_class_method :check_valid_url
49
+ end
50
+ end
@@ -0,0 +1,155 @@
1
+ require 'nokogiri'
2
+
3
+ module XIVLodestone
4
+ class Parser
5
+ # An exception for a invalid Nokogiri page
6
+ class InvalidDocument < Exception
7
+ end
8
+ # default constructor, initalises @page with a
9
+ # Nokogiri document. Raises a InvalidDocument eception
10
+ # if page is invalid
11
+ def initialize(page)
12
+ fail InvalidDocument, 'Invalid page' if page.nil?
13
+ @page = page
14
+ end
15
+ # Returns a #Hash of the characters class levels
16
+ # Example { :Gladitor => 10, ... }
17
+ # If no classes found returns a empty #Hash
18
+ def get_classes()
19
+ class_list = Hash.new
20
+ @page.xpath('//table[@class="class_list"]/tr/td').each_slice(3) do |td|
21
+ # Not a valid class
22
+ next if td[0].text.empty?
23
+
24
+ name = td[0].text
25
+ level = td[1].text.to_i
26
+ exp = td[2].text.split(/\//)
27
+ icon = td[0].at_css('img')['src']
28
+
29
+ class_list[name.downcase.to_sym] = [name, level, exp[0].to_i,
30
+ exp[1].to_i, icon]
31
+ end
32
+ class_list
33
+ end
34
+ # Returns a #Hash of the character attributes
35
+ # Example { :str => 243, ... }
36
+ # If no attributes found returns a empty #Hash
37
+ def get_attributes()
38
+ stats = Hash.new
39
+ @page.xpath('//div[starts-with(@class, "param_left_area_inner")]/ul/li').each_with_index do |li, index|
40
+ if index < 6
41
+ stats[replace_downcase(li['class']).to_sym] = li.text.to_i
42
+ else
43
+ ele = li.text.split(/(?<=\D)(?=\d)/)
44
+ stats[replace_downcase(ele[0]).to_sym] = ele[1].to_i
45
+ end
46
+ end
47
+ stats
48
+ end
49
+ # Returns a #Hash of the characters gear list
50
+ # Example { :head => [ "item_name", "item_url" ], ... }
51
+ # If no gear found returns a empty #Hash
52
+ def get_gear()
53
+ # TODO: Smelly code, rewrite
54
+ items = Hash.new
55
+ ring_count = 1
56
+ @page.xpath("(//div[@class='item_detail_box'])[position() < 13]").each_with_index do |item, index|
57
+ type = get_item_type(item.at_css('h3.category_name').text)
58
+ level = item.xpath('//div[@class="pt3 pb3"]')[index].text.split(/ /).last.to_i
59
+ if type.eql? "ring"
60
+ items["#{type}#{ring_count}".to_sym] = [item.css('h2').text, level, type, "http://na.finalfantasyxiv.com#{item.css('a')[0]['href']}"]
61
+ ring_count += 1
62
+ else
63
+ items[replace_downcase(type).to_sym] = [item.css('h2').text, level, type, "http://na.finalfantasyxiv.com#{item.css('a')[0]['href']}"]
64
+ end
65
+ end
66
+ items
67
+ end
68
+ # Returns a string representing what item it is
69
+ def get_item_type(item_name)
70
+ if item_name =~ /(Arm|Arms|Grimoire|Primary Tool)/i
71
+ return "weapon"
72
+ elsif item_name =~ /Shield/i
73
+ return "shield"
74
+ else
75
+ return item_name.downcase
76
+ end
77
+ end
78
+ # Returns a #Integer of the characters hp
79
+ # otherwise returns nil
80
+ def get_hp()
81
+ hp = @page.at_xpath('//li[@class="hp"]')
82
+ hp.nil? ? nil : hp.text.to_i
83
+ end
84
+ # Returns a #Integer of the characters mp
85
+ # otherwise returns nil
86
+ def get_mp()
87
+ mp = @page.at_xpath('//li[@class="mp"]')
88
+ mp.nil? ? nil : mp.text.to_i
89
+ end
90
+ # Returns a #Integer of the characters tp
91
+ # otherwise returns nil
92
+ def get_tp()
93
+ tp = @page.at_xpath('//li[@class="tp"]')
94
+ tp.nil? ? nil : tp.text.to_i
95
+ end
96
+ # Returns a #String of the characters sex
97
+ # example "Male" or "Female"
98
+ # otherwise returns nil
99
+ def get_sex()
100
+ sex = @page.at_xpath('//div[@class="chara_profile_title"]')
101
+ if sex.nil?
102
+ nil
103
+ else
104
+ sex.text.split(/\//).last =~ /♂/i ? "Male" : "Female"
105
+ end
106
+ end
107
+ # Returns a #String of the characters race
108
+ # otherwise returns nil
109
+ def get_race()
110
+ race = @page.at_xpath('//div[@class="chara_profile_title"]')
111
+ race.nil? ? nil : race.text.split(/\//).first.strip!
112
+ end
113
+ # Returns a #String of the characters clan
114
+ # otherwise returns nil
115
+ def get_clan()
116
+ clan = @page.at_xpath('//div[@class="chara_profile_title"]')
117
+ clan.nil? ? nil : clan.text.split(/\//)[1].strip!
118
+ end
119
+ # Returns a #String with the nameday
120
+ # otherwise returns nil
121
+ def get_nameday()
122
+ nameday = @page.at_xpath('(//div[@class="chara_profile_table"]/dl/dd)[1]')
123
+ nameday.nil? ? nil : nameday.text
124
+ end
125
+ # Returns a #String with the guardian name
126
+ # otherwise returns nil
127
+ def get_guardian()
128
+ guardian = @page.at_xpath('(//div[@class="chara_profile_table"]/dl/dd)[2]')
129
+ guardian.nil? ? nil : guardian.text
130
+ end
131
+ # Returns a #String the city name
132
+ # otherwise returns nil
133
+ def get_city()
134
+ city = @page.at_xpath('(//dd[@class="txt_name"])[1]')
135
+ city.nil? ? nil : city.text
136
+ end
137
+ # Returns a #String with the grandcompany
138
+ # otherwise returns nil
139
+ def get_grand_company()
140
+ company = @page.at_xpath('(//dd[@class="txt_name"])[2]')
141
+ company.nil? ? nil : company.text
142
+ end
143
+ # Returns a #Array with the freecompany name and url
144
+ # otherwise returns nil
145
+ def get_free_company()
146
+ element = @page.at_xpath('//dd[@class="txt_name"]/a')
147
+ element.nil? ? nil : [ element.text, "http://na.finalfantasyxiv.com#{element['href']}" ]
148
+ end
149
+ # Replaces spaces wtih underscores, and downcases
150
+ # Returns a #String
151
+ def replace_downcase(string)
152
+ string.gsub(" ", "_").downcase
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,4 @@
1
+ # Stores the current version number of the gem
2
+ module XIVLodestone
3
+ VERSION = '0.0.2'
4
+ end
@@ -0,0 +1,8 @@
1
+ require 'xiv_lodestone/version'
2
+ require 'xiv_lodestone/lodestone_helper'
3
+ require 'xiv_lodestone/lodestone_character'
4
+ require 'xiv_lodestone/lodestone_parser'
5
+
6
+ # TODO
7
+ module XIVLodestone
8
+ end