wopian/tracker-killer

View on GitHub
src/api/kitsu.js

Summary

Maintainability
B
6 hrs
Test Coverage
import Kitsu from 'kitsu'
import OAuth2 from 'client-oauth2'
import moment from 'moment'
import { version } from '../../package'
import { logFile, logList } from '../util'
import { KITSU } from '../env'

const api = new Kitsu({
  headers: {
    'user-agent': `trackerKiller/${version} (https://github.com/wopian/tracker-killer)`
  }
})

let userId, lastAction

export default class Api {
  constructor (type) {
    lastAction = moment()
    this.requests = 0
    this.type = type.toLowerCase()
    this.setup()
  }

  async log (ID = '', action = '', last, level = 'info', requests = this.requests, err = '') {
    logFile(level, 'Kitsu', this.type, ID, action, err, requests)
    this.ondata(logList(ID, action, last, requests))
    lastAction = moment()
  }

  async setup () {
    if (!KITSU.USERNAME || KITSU.USERNAME === 'yourUsername' ||
        !KITSU.PASSWORD || KITSU.PASSWORD === 'yourPassword' ||
        !KITSU.CLIENT_ID || KITSU.CLIENT_ID === 'yourClientID' ||
        !KITSU.CLIENT_SECRET || KITSU.CLIENT_SECRET === 'yourClientSecret') {
      console.log('\nKitsu needs some user info to authorise\n\n' +
        '1. Rename env.template.js to env.js\n' +
        '2. Add username, password, client ID & secret for Kitsu')
      process.exit(0)
    } else this.auth()
  }

  async auth () {
    try {
      const { owner } = new OAuth2({
        clientId: KITSU.CLIENT_ID,
        clientSecret: KITSU.CLIENT_SECRET,
        accessTokenUri: 'https://kitsu.io/api/oauth/token'
      })
      const { data } = await owner.getToken(KITSU.USERNAME, KITSU.PASSWORD)
      api.headers['Authorization'] = `Bearer ${data.access_token}`
      this.log(undefined, `Connected to ${KITSU.USERNAME}`, lastAction, 'info')
      await this.getUser()
      await this.getMedia()
    } catch (err) {
      throw err
    }
  }

  async getUser () {
    try {
      let { id } = await api.self()
      userId = await id
      this.log(undefined, `Linked ${KITSU.USERNAME} with ${userId}`, lastAction, 'info')
    } catch (err) {
      this.log(undefined, `Error getting ID of ${KITSU.USERNAME}`, lastAction, 'error', err)
    }
  }

  async getMedia (offset = 0) {
    try {
      const { data, links } = await api.get(this.type, {
        fields: { anime: 'id', manga: 'id' },
        page: { limit: 20, offset }
      })

      this.log(`${offset}-${offset + 20}`, 'Fetched', lastAction, 'trace', ++this.requests)
      await this.addLibraryEntry(data)
      // Load next page if it exists
      if (links.next) await this.getMedia(offset + 20)
      else if (this.oncomplete) {
        this.log(undefined, 'Finished', undefined, 'info', this.requests)
        this.oncomplete()
      }
    } catch (err) {
      this.log(`${offset}-${offset + 20}`, 'Failed', lastAction, 'error', ++this.requests, `Error fetching media - ${JSON.stringify(err)}`)
    }
  }

  // Create a new library entry
  async addLibraryEntry (media) {
    for (let mediaEntry in await media) {
      try {
        await api.post('libraryEntries', {
          media: { type: this.type, id: media[mediaEntry].id },
          user: { id: userId },
          status: 'planned',
          progress: 0
        })

        this.log(media[mediaEntry].id, 'Added', lastAction, 'trace', ++this.requests)
      } catch (err) {
        if (err.errors && err.errors.constructor === Array) {
          for (let error of await err.errors) {
            // User already has media in their library
            if (error.title === 'has already been taken') {
              this.log(media[mediaEntry].id, 'Exists', lastAction, 'trace', ++this.requests, 'Already in library')
              await this.getLibraryEntry(media[mediaEntry])
            }
          }
        } else {
          this.log(media[mediaEntry].id, 'Failed', lastAction, 'error', ++this.requests, `Error adding library entry - ${JSON.stringify(err)}`)
        }
      }
    }
  }

  // Get library entry to find its ID
  async getLibraryEntry (mediaEntry) {
    try {
      let { data } = await api.get('libraryEntries', {
        filter: { userId, kind: this.type, mediaId: mediaEntry.id },
        page: { limit: 1 }
      })
      this.log(mediaEntry.id, 'Entry', lastAction, 'trace', ++this.requests, 'Library entry ID')
      await this.updateLibraryEntry(mediaEntry, data[0].id)
    } catch (err) {
      this.log(mediaEntry.id, 'Failed', lastAction, 'error', ++this.requests, `Error fetching library entry - ${err}`)
    }
  }

  async updateLibraryEntry (mediaEntry, entryId) {
    try {
      api.patch('libraryEntries', {
        id: entryId,
        status: 'planned',
        progress: 0
      })
      this.log(mediaEntry.id, 'Updated', lastAction, 'trace', ++this.requests)
    } catch (err) {
      if (err.errors && err.errors.constructor === Array) {
        err.errors.forEach(error => {
          this.log(mediaEntry.id, 'Failed', lastAction, 'error', ++this.requests, `Error adding library entry - ${error}`)
        })
      } else this.log(mediaEntry.id, 'Failed', lastAction, 'error', ++this.requests, `Error adding library entry - ${JSON.stringify(err)}`)
    }
  }
}