app/models/list_import/my_anime_list.rb
class ListImport
class MyAnimeList < ListImport
class ResponseError < StandardError; end
class RateLimitedError < ResponseError; end
MAL_HOST = 'https://myanimelist.net'.freeze
# Only accept usernames, not XML exports
validates :input_text, presence: true
validates :input_file_data, absence: true
validate :ensure_list_is_public, on: :create
def count
data.length
end
def ensure_list_is_public
%w[anime manga].each do |kind|
request = HTTP.get("#{MAL_HOST}/#{kind}list/#{input_text}")
case request.status
when 403
errors.add(:input_text,
"Your MyAnimeList #{kind} list must be public to import")
when 404
errors.add(:input_text, 'MyAnimeList user not found')
end
end
end
def each
data.each do |row|
row = Row.new(row, date_format)
yield row.media, row.data
end
end
private
def date_format
return @date_format if @date_format
# if any dates have values higher than 12, assume the date format
data.each do |row|
row.fetch_values('start_date_string', 'finish_date_string').each do |date|
next unless date.present?
place1, place2 = date.split('-').map(&:to_i)
return @date_format = '%d-%m-%y' if place1 > 12
return @date_format = '%m-%d-%y' if place2 > 12
end
end
nil
end
def data
@data ||= %w[animelist mangalist].map { |l| list(l) }.reduce(&:+)
end
def list(list)
loop.with_index.reduce([]) do |data, (_, index)|
begin
page = get(list, index)
rescue RateLimitedError
sleep 10
redo
end
break data if page.blank?
sleep 2
data + page
end
end
def get(list, page)
res = HTTP.get(build_url(list, page))
raise RateLimitedError.new(res.status_message) if res.status == 429
raise ResponseError.new(res.status_message) unless res.status.success?
JSON.parse(res.body)
end
def build_url(list, page)
offset = page * 300
"#{MAL_HOST}/#{list}/#{input_text}/load.json?offset=#{offset}&status=7"
end
end
end