foxit 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 541b370e85c872627d4bff2419a6af1d8ce922c8e19363218aad0f6642b435ab
4
+ data.tar.gz: 74738a7174d332d718573188510a6e4744f8b08b7cb66735ae758fadb65dfd1c
5
+ SHA512:
6
+ metadata.gz: 4dcfdbcf7d13ec79d4501298fe49379b07fff9736829a42f01ecaee043aa0ccfde9a1bd6861c8ea63bb0824cf6f134a1ec81e9d5f83956388a86420e4d872ca9
7
+ data.tar.gz: e597208dcabad4affd0ff242bbcec7cddc7ae85afb0f499193d85b82dbf9885a0cb32b2719795907d60608c21c55671ea5daf32c3356ccc4e24b0a2c0f18f974
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+
3
+ /scratch
4
+ /notebooks
5
+ /examples
6
+ /.vscode
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Lachlan Taylor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # Kitsu API Wrapper
2
+ Unofficial Ruby wrapper for [Kitsu][kitsu] API ([documentation][api]).
3
+
4
+ Note: this is the initial version and was written primarily to store data locally on which to train recommendation models. Hence, it works well for the task it was created but there are a few gaps that could be included without too much effort.
5
+
6
+
7
+ ## Installation
8
+ ```
9
+ gem install foxit
10
+ ```
11
+
12
+ ## Usage
13
+ A few quick examples are detailed below:
14
+
15
+ ### Retrieving Data
16
+ Get an anime item by id.
17
+ ```ruby
18
+ api = Foxit::API.new()
19
+ result = api.get_anime_by_id(1)
20
+ ```
21
+
22
+ This returns the full json response; however, an object based result can be returned as per the following (planned to be returned as default)
23
+ ```ruby
24
+ item = Anime.new(result['data'])
25
+ ```
26
+
27
+ Similarly, getting a users library. This returns a list of entries from the `'data'` attribute of the json returned.
28
+ ```ruby
29
+ result = api.get_library_by_id(1)
30
+ ```
31
+
32
+
33
+ ### Storing Data with MongoDB
34
+ If you don't have mongodb installed you can download the free community version from [here][mongodb]. Once installed, you need to start the service via terminal:
35
+ ```
36
+ mongod --port 27017 --dbpath /path/to/db
37
+ ```
38
+ Note: you can stop it by pressing `ctrl+c` when the terminal is active or by using
39
+ ```
40
+ mongod --dbpath /path/to/db --shutdown
41
+ ```
42
+
43
+ Once you have mongodb running you can retreive and store data by using the following functions.
44
+
45
+ **Please use responsibly!** Library results are especially expensive as they require a secondary call to lookup the media_id from the library item id that is returned in the user library request. In other words, for the benefit of everyone, don't punish the API with hundreds of calls.
46
+
47
+ ```ruby
48
+ etl = Foxit::ETL.new()
49
+ etl.get_anime(1..100) # stores anime item results from media_id 1 to 100
50
+ etl.get_libraries(1..25) # stores user library results from user_id 1 to 25
51
+ ```
52
+
53
+ #### MongoDB Library Item
54
+ Current fields obtained from Kitsu API. There are more available and could easilty be added; however, this was all I needed for working on the recommendation models.
55
+ ```
56
+ {
57
+ '_id': ObjectId('5b56ad7dafd2a496c0faa495'),
58
+ 'media_id': 1376, # id for anime, e.g., this one relates to 'Code Geass: Lelouch of the Rebellion'
59
+ 'rating': 17, # user rating [1-20]; None if not rated.
60
+ 'record_id': 18344699, # library item id; used to return the actual media_id
61
+ 'status': 'completed', # media viewing status: {completed, planned, watching, hold, dropped}
62
+ 'type': 'anime', # media type: {Anime, Manga, Drama?}.
63
+ 'user_id': 1 # user id
64
+ }
65
+ ```
66
+
67
+
68
+ #### MongoDB Anime Item
69
+
70
+ ```
71
+ {
72
+ '_id': ObjectId('5b570e4eafd2a41db810927b'),
73
+ 'avg_rating': 84.36,
74
+ 'end_date': '1999-04-24',
75
+ 'id': 1,
76
+ 'n_favourites': 0,
77
+ 'n_users': 71919,
78
+ 'nsfw': False,
79
+ 'rank_popularity': 15,
80
+ 'rank_rating': 27,
81
+ 'rating_freq': { '10': 415,
82
+ '11': 27,
83
+ '12': 1700,
84
+ '13': 53,
85
+ '14': 3878,
86
+ '15': 139,
87
+ '16': 5255,
88
+ '17': 255,
89
+ '18': 6261,
90
+ '19': 238,
91
+ '2': 1566,
92
+ '20': 20974,
93
+ '3': 33,
94
+ '4': 351,
95
+ '5': 15,
96
+ '6': 115,
97
+ '7': 16,
98
+ '8': 1545,
99
+ '9': 22},
100
+ 'showtype': 'TV',
101
+ 'slug': 'cowboy-bebop',
102
+ 'start_date': '1998-04-03',
103
+ 'subtype': 'TV',
104
+ 'synopsis': 'In the year 2071, humanity has colonized several of the planets '
105
+ 'and moons of the solar system leaving the now uninhabitable '
106
+ 'surface of planet Earth behind. The Inter Solar System Police '
107
+ 'attempts to keep peace in the galaxy, aided in part by outlaw '
108
+ 'bounty hunters, referred to as "Cowboys". The ragtag team '
109
+ 'aboard the spaceship Bebop are two such individuals.\r\n'
110
+ 'Mellow and carefree Spike Spiegel is balanced by his '
111
+ 'boisterous, pragmatic partner Jet Black as the pair makes a '
112
+ 'living chasing bounties and collecting rewards. Thrown off '
113
+ 'course by the addition of new members that they meet in their '
114
+ 'travels�Ein, a genetically engineered, highly intelligent Welsh '
115
+ 'Corgi; femme fatale Faye Valentine, an enigmatic trickster with '
116
+ 'memory loss; and the strange computer whiz kid Edward Wong�the '
117
+ "crew embarks on thrilling adventures that unravel each member's "
118
+ 'dark and mysterious past little by little. \r\n'
119
+ 'Well-balanced with high density action and light-hearted '
120
+ 'comedy, Cowboy Bebop is a space Western classic and an homage '
121
+ 'to the smooth and improvised music it is named after. \r\n'
122
+ '[Written by MAL Rewrite]',
123
+ 'title': 'Cowboy Bebop'
124
+ }
125
+ ```
126
+
127
+
128
+ ## Further Work
129
+ - Add some more detail/examples to README
130
+ - Extend on functionality/usability for benefit of other users.
131
+
132
+
133
+
134
+ [kitsu]: https://kitsu.io
135
+ [api]: https://kitsu.docs.apiary.io/
136
+ [mongodb]: https://www.mongodb.com/download-center#community
data/foxit.gemspec ADDED
@@ -0,0 +1,29 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ # require "foxit/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "foxit"
8
+ # spec.version = Foxit::VERSION
9
+ spec.version = "0.1.1"
10
+ spec.authors = ["Lachlan Taylor"]
11
+ spec.email = ["lachlanbtaylor@gmail.com"]
12
+
13
+ spec.summary = %q{Unofficial Kitsu API wrapper with MongoDB capability.}
14
+ # spec.description = %q{Write a longer description or delete this line.}
15
+ spec.homepage = "https://github.com/rokkuran/foxit"
16
+ spec.license = "MIT"
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_dependency 'json', '~> 2.1.0', '>= 2.0.4'
27
+ spec.add_dependency 'addressable', '~> 2.5.2', '>= 2.5.2'
28
+ spec.add_dependency 'mongo', '~> 2.5.1', '>= 2.5.1'
29
+ end
data/lib/foxit/api.rb ADDED
@@ -0,0 +1,179 @@
1
+ require_relative 'objects'
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'addressable/uri'
6
+
7
+
8
+
9
+ module Foxit
10
+
11
+ class API
12
+
13
+ def initialize
14
+ @root = "https://kitsu.io/api/edge/"
15
+ end
16
+
17
+
18
+ def build_library_url id, type='Anime', status='completed', limit=500
19
+ uri = Addressable::URI.parse("#{@root}library-entries")
20
+
21
+ uri_query = {
22
+ "filter[user_id]" => id,
23
+ "filter[media_type]" => type,
24
+ "filter[status]" => status,
25
+ "page[limit]" => limit
26
+ }
27
+ uri.query_values = uri_query
28
+
29
+ uri.to_s
30
+ end
31
+
32
+
33
+ def build_media_url entry_id
34
+ "#{@root}/library-entries/#{entry_id}/relationships/media"
35
+ end
36
+
37
+
38
+ def get_result url
39
+ response = Net::HTTP.get(URI.parse(url))
40
+ JSON.parse(response)
41
+ end
42
+
43
+
44
+ def get_library_by_url url, entries=[]
45
+
46
+ result = self.get_result(url)
47
+ entries += result['data']
48
+
49
+ if result['links'].key?('next')
50
+ # recursion to retrieve additional results that have ben paginated
51
+ result = self.get_library_by_url(result['links']['next'], entries)
52
+ else
53
+ return entries
54
+ end
55
+ end
56
+
57
+
58
+ def get_library_by_id user_id
59
+ url = self.build_library_url(user_id)
60
+ self.get_library_by_url(url)
61
+ end
62
+
63
+
64
+ def get_media_relationship_by_id entry_id
65
+ # adding memoisation to stop requesting same media docs
66
+ # TODO: extend to items already existing in db?
67
+ @get_media_relationship_by_id ||= {}
68
+ return @get_media_relationship_by_id[entry_id] if @get_media_relationship_by_id.key?(entry_id)
69
+
70
+ url = self.build_media_url(entry_id)
71
+ self.get_result(url)
72
+ end
73
+
74
+
75
+ def batch_get_results ids, fn, max_threads
76
+ """
77
+ ids: ids of results returned: user_id | library_entry_id in this case.
78
+ fn: function name to use to return results (need to use symbol method name)
79
+ e.g. :get_result
80
+ max_threads: maximum number of active threads.
81
+ """
82
+
83
+ results = {}
84
+ threads = []
85
+
86
+ ids.each do |id|
87
+
88
+ if Thread.list.count % max_threads != 0
89
+ thread = Thread.new do
90
+ # adding lock slows down considerably shouldn't matter as results are written to hash?
91
+ results[id] = send(fn, id)
92
+ end
93
+ threads << thread
94
+ else
95
+ # wait for open threads to finish before starting new one
96
+ threads.each(&:join)
97
+
98
+ thread = Thread.new do
99
+ results[id] = send(fn, id)
100
+ end
101
+ threads << thread
102
+ end
103
+
104
+ end
105
+
106
+ threads.each(&:join)
107
+
108
+ results
109
+ end
110
+
111
+ # TODO: should probably assign to a @variable
112
+ def batch_get_libraries user_ids, max_threads=200
113
+
114
+ all_library_entries = []
115
+ user_libraries = self.batch_get_results(user_ids, :get_library_by_id, max_threads)
116
+
117
+ user_libraries.each do |user_id, library|
118
+
119
+ media_ids = []
120
+ library.map { |entry| media_ids << entry['id'] }
121
+ media_results = self.batch_get_results(media_ids, :get_media_relationship_by_id, max_threads)
122
+
123
+ library.each do |entry|
124
+ all_library_entries << LibraryItem.new(user_id, entry, media_results[entry['id']])
125
+ end
126
+
127
+ end
128
+
129
+ all_library_entries
130
+ end
131
+
132
+
133
+ def get_user_library
134
+ # TODO: return user library using single id call to batch_get_library
135
+ end
136
+
137
+
138
+ def batch_get_libraries_docs user_ids, max_threads=200
139
+ all_library_entries = self.batch_get_libraries(user_ids, max_threads)
140
+
141
+ docs = []
142
+ all_library_entries.map { |entry| docs << entry.to_hash }
143
+
144
+ docs
145
+ end
146
+
147
+
148
+ def build_anime_url id
149
+ "#{@root}/anime/#{id}"
150
+ end
151
+
152
+
153
+ def get_anime_by_id id
154
+ url = build_anime_url(id)
155
+ self.get_result(url)
156
+ end
157
+
158
+
159
+ def batch_get_anime anime_ids, max_threads=200
160
+ results = self.batch_get_results(anime_ids, :get_anime_by_id, max_threads)
161
+
162
+ anime_items = []
163
+ results.each do |id, result|
164
+ unless result.key?('errors')
165
+ anime_items << Anime.new(result['data'])
166
+ end
167
+ end
168
+
169
+ anime_items
170
+ end
171
+
172
+ def get_anime_documents anime_ids, max_threads=200
173
+ anime_items = self.batch_get_anime(anime_ids, max_threads)
174
+ self.objects_to_hash(anime_items)
175
+ end
176
+
177
+ end
178
+
179
+ end
data/lib/foxit/etl.rb ADDED
@@ -0,0 +1,43 @@
1
+ require_relative 'api'
2
+
3
+ require 'mongo'
4
+ Mongo::Logger.logger.level = Logger::WARN
5
+
6
+
7
+
8
+ module Foxit
9
+
10
+ class ETL
11
+ attr_reader :kitsu, :client
12
+
13
+ def initialize db_name: 'kitsu', host: ['127.0.0.1:27017']
14
+ @kitsu = API.new()
15
+ @client = Mongo::Client.new(host, {database: db_name})
16
+ end
17
+
18
+ def insert_many_docs collection_name, docs
19
+ begin
20
+ puts "inserting..."
21
+ result = @client[collection_name].insert_many(docs)
22
+ puts "records inserted: #{result.inserted_count}"
23
+ rescue StandardError => e
24
+ puts "error: #{e}"
25
+ end
26
+ puts "complete.\n"
27
+ end
28
+
29
+
30
+ def get_libraries user_ids
31
+ docs = @kitsu.batch_get_libraries_docs(user_ids)
32
+ insert_many_docs('library', docs)
33
+ end
34
+
35
+
36
+ def get_anime media_ids
37
+ docs = @kitsu.get_anime_documents(media_ids)
38
+ insert_many_docs('anime', docs)
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,13 @@
1
+ class Helpers
2
+ def to_hash
3
+ hash = {}
4
+ instance_variables.each {|x| hash[x.to_s.delete("@")] = instance_variable_get(x)}
5
+ hash
6
+ end
7
+
8
+ def objects_to_hash obj_array
9
+ docs = []
10
+ obj_array.map { |obj| docs << obj.to_hash }
11
+ docs
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ require_relative 'helpers'
2
+
3
+
4
+
5
+ class LibraryItem < Helpers
6
+ attr_reader :user_id, :record_id, :status, :rating, :media_id, :type
7
+
8
+ def initialize user_id, entry_result, media_result
9
+ @user_id = user_id.to_i
10
+ @record_id = entry_result['id'].to_i
11
+ @status = entry_result['attributes']['status']
12
+
13
+ rating = entry_result['attributes']['ratingTwenty']
14
+ @rating = rating.nil? ? nil : rating.to_i
15
+
16
+ @media_id = media_result['data']['id'].to_i
17
+ @type = media_result['data']['type']
18
+ end
19
+ end
20
+
21
+
22
+ class Anime < Helpers
23
+ attr_reader :id, :slug, :synopsis, :title, :avg_rating, :rating_freq, :n_users,
24
+ :n_favourites, :start_date, :end_date, :rank_popularity, :rank_rating, :subtype,
25
+ :nsfw; :showtype
26
+
27
+ def initialize data
28
+ attributes = data['attributes']
29
+ @id = data['id'].to_i
30
+ @slug = attributes['slug']
31
+ @synopsis = attributes['synopsis']
32
+ @title = attributes['canonicalTitle']
33
+ @avg_rating = attributes['averageRating'].to_f # TODO: need to handle nil values?
34
+
35
+ rf_int = {}
36
+ # mongodb needs string keys anyway, so k.to_i redundant...
37
+ attributes['ratingFrequencies'].each_pair { | k, v | rf_int[k.to_i] = v.to_i }
38
+ @rating_freq = rf_int
39
+
40
+ @n_users = attributes['userCount'].to_i
41
+ @n_favourites = attributes['favouritesCount'].to_i
42
+ @start_date = attributes['startDate']
43
+ @end_date = attributes['endDate']
44
+ @rank_popularity = attributes['popularityRank'].to_i
45
+ @rank_rating = attributes['ratingRank'].to_i
46
+ @subtype = attributes['subtype']
47
+ @showtype = attributes['showType']
48
+ @nsfw = attributes['nsfw'] # TODO: convert to bool?
49
+ end
50
+ end
data/lib/foxit.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "foxit/objects"
2
+ require "foxit/helpers"
3
+
4
+
5
+ module Foxit
6
+ VERSION = "0.1.0"
7
+
8
+ require "foxit/api"
9
+ require "foxit/etl"
10
+
11
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: foxit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Lachlan Taylor
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-07-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.1.0
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.4
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 2.1.0
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.4
33
+ - !ruby/object:Gem::Dependency
34
+ name: addressable
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 2.5.2
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 2.5.2
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 2.5.2
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 2.5.2
53
+ - !ruby/object:Gem::Dependency
54
+ name: mongo
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 2.5.1
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 2.5.1
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 2.5.1
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 2.5.1
73
+ description:
74
+ email:
75
+ - lachlanbtaylor@gmail.com
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - ".gitignore"
81
+ - Gemfile
82
+ - LICENSE
83
+ - README.md
84
+ - foxit.gemspec
85
+ - lib/foxit.rb
86
+ - lib/foxit/api.rb
87
+ - lib/foxit/etl.rb
88
+ - lib/foxit/helpers.rb
89
+ - lib/foxit/objects.rb
90
+ homepage: https://github.com/rokkuran/foxit
91
+ licenses:
92
+ - MIT
93
+ metadata: {}
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.7.7
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Unofficial Kitsu API wrapper with MongoDB capability.
114
+ test_files: []