<commit_before>var express = require('express');
var router = express.Router();
const fs = require('fs');
const ejs = require('ejs');
const pool = require('../config/db_pool');
// var aws = require('aws-sdk');
// aws.config.loadFromPath('./config/aws_config.json');

/* GET home page. */
// router.get('/', function(req, res, next) {
//     res.render('index');
// });

router.get('/', function(req, res, next) {
  fs.readFile('./views/index.ejs', 'utf-8', function(err, result) {
    if (err)
    console.log('reading ejs error : ', err);
    else
    res.status(200).send(ejs.render(result));
  });
});

router.post('/classConfirm', function(req, res) {

  function myRandom(max, min) {
    return Math.floor(Math.random()*(max-min)) + min;
  }
  var rand = myRandom(1000,9999);

  console.log('연결');
  req.session.className = req.body.className + rand;
  className=req.session.className;
  likeMinimum = req.body.likeMinimum;
  if (likeMinimum.length <= 0)
  likeMinimum = 0;

  console.log(className);
  console.log(likeMinimum);

  pool.getConnection(function(error, connection){
    console.log('들어왔어요');
    if (error)
    {
      console.log("getConnection Error" + error);
      res.sendStatus(503);
    }

    else {
      connection.query('select count(*) as exist from class where className = ?', [className], function(error, row)
      {
        if(error)
        {
          console.log("getConnection Error" + error);
          res.sendStatus(503);
        }
        else
        {
          if(row[0].exist != 0)
          {
            console.log('현존하는 강의가 존재합니다.');
            res.render('index');
          }

          else
          {
            connection.query('insert into class values(null,?,?,?)', [rand, className, likeMinimum], function(error, comments)
            {
              console.log(comments);
              if(error) console.log('selecting query err: ', error);
              else{
                res.render('className', {className : req.session.className});
                connection.release();
              }
            });
          }
        }
      });
    }
  });
});

router.get('/questionList/:className', function(req, res){
  pool.getConnection(function(error, connection){
    console.log('들어왔어요');
    if (error)
    {
      console.log("getConnection Error" + error);
      res.sendStatus(503);
      //connection.release();
    } else {
      connection.query('select id from class where className =?', req.session.className, function(error, row)
      {
        if(error) console.log("selecting Error" + error);
        else {
          console.log(row[0].id);
          req.session.classID = row[0].id;
          connection.query('select id, classID, userID, content, time, commentCnt, likeCnt from question where classID = ?', [row[0].id] , function(error, result) {
            if(error) console.log("selecting Error" + error);
            else
            {
              if(result.length == 0){
                console.log('텅텅 비었어요');
                res.render('realclass', {className : req.session.className, question : null});
              }
              else{
                console.log(result);
                res.render('realClass', {className : req.session.className, question : result});
              }
              connection.release();
            }
          });
        }

      });
    }
  });
});

router.get('/classDetail/:questionID', function(req, res){
  questionID = req.params.questionID;
  className = req.session.className;
  classId = req.session.classID;
  console.log(questionID);
  console.log(classId);
  pool.getConnection(function(error, connection){
    console.log('들어왔어요');
    if (error)
    {
      console.log("getConnection Error" + error);
      res.sendStatus(503);
      //connection.release();
    }

    else {
      connection.query('select * from question where question.id=?', [questionID], function(error, questionData)
      {
        if(error) console.log("selecting Error" + error);
        else {
          console.log('넘어갑니다.');

          connection.query('select * from comment where comment.questionID=?', [questionID], function(error, comments) {
            console.log(questionData);
            console.log(comments);
            res.render('classDetail', {className : req.session.className, questionInfo : questionData, commentsInfo : comments});
          })
        }
      });
    }
    connection.release();
  });
});

router.post('/end', function(req, res){
  req.session.destroy(function(err){
    if(err) console.error('err', err);
    res.redirect('/');
  });
});

/* restful api */
router.post('/api/join', function(req, res){
  console.log(req.body.id);
  console.log(req.body.pw);
  console.log(req.body.email);
  if (req.body.id == null || req.body.pw == null || req.body.email == null) {
    res.json({success:false});
  } else {
    pool.getConnection(function(error, connection){
      if (error)
      {
        console.log("getConnection Error" + error);
        res.sendStatus(503);
      } else {
        connection.query('insert into user values(null, ?, ?, ?)', [req.body.id, req.body.pw, req.body.email], function(err, result) {
          console.log(result);
          if (err != null) {
            console.log('selecting query err: ', err);
            res.json({success:false, message:"중복된 정보가 존재합니다."});
          } else {
            res.json({success:true});
          }
        });
      }
      connection.release();
    });
  }
});

router.post('/api/login', function(req,res){
  console.log(req.body.id);
  console.log(req.body.pw);
  if(req.body.id ==null || req.body.pw==null){
    res.json({sucess:false});
  } else {
    pool.getConnection(function(error, connection){
      if (error)
      {
        console.log("getConnection Error"+ error);
        res.sendStatus(503);
      } else {
        connection.query('select * from user where userID=?',[req.body.id], function(error, result){
          if (error != null) {
            res.json({success:false, message:"Internal Error"});
          } else {
            console.log(result);
            if (result.length <= 0) {
              res.json({success:false, message:"입력하신 ID가 없습니다."});
            } else {
              if (result[0].userPW == req.body.pw) {
                req.session.userID = result[0].id;
                req.session.userName = req.body.id;
                res.json({success:true, message :"로그인 성공", userID:result[0].id});
              } else {
                res.json({success:false, message:"비밀번호가 일치하지 않습니다."});
              }
            }
          }
          connection.release();
        });
      }
    });
  }
});

router.post('/api/logout',function(req, res){
  req.session.destroy(function(){
    req.session;
  });
  res.json({success:true});
});

router.get('/api/classList', function(req, res) {
  console.log('API: get classlist');
  pool.getConnection(function(error, connection) {
    if (error) {
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('select * from class', function(err, result) {
        console.log(result);
        if (err == null) {
          res.json({
            success:true,
            count:result.length,
            classinfos:result
          });
        } else {
          res.json({success:false});
        }
      });
      connection.release();
    }
  });
});

router.post('/api/makeQuestion', function(req, res) {
  console.log('API: post makeQuestion');
  console.log(req.body.classID);
  console.log(req.body.userID);
  console.log(req.body.content);
  pool.getConnection(function(error, connection) {
    if (error) {
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('insert into question (classID, userID, content) values (?, ?, ?)', [req.body.classID, req.body.userID, req.body.content], function(err, result) {
        console.log(result);
        if (err != null) {
          console.log('insert query err: ', err);
          res.json({success:false});
        } else {
          res.json({success:true});
        }
      });
      connection.release();
    }
  });
});

router.get('/api/questionList/:classID', function(req, res) {
  console.log('API: get questinoList');
  console.log(req.params.classID);
  pool.getConnection(function(error, connection) {
    if (error) {
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('select * from question where classID=?', [req.params.classID], function(err, result) {
        console.log(result);
        if (err == null) {
          res.json({
            success:true,
            count:result.length,
            questionInfos:result
          });
        } else {
          res.json({success:false});
        }
      });
      connection.release();
    }
  });
});

router.get('/api/questionDetail/:questionID', function(req, res) {
  console.log('API: get question detail');
  pool.getConnection(function(error, connection) {
    if (error) {
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('select * from question where id=?', [req.params.questionID], function(err, result) {
        if (err == null) {
          console.log(result);
          console.log(req.params.questionID);
          console.log(req.session.userID);
          connection.query('select * from heart where (questionID=? AND userID=?)', [req.params.questionID, req.session.userID], function(err2, result2) {
            if (err2 == null) {
              console.log(result2);
              res.json({
                success:true,
                info:result,
                didLike: (result2.length!=0)
              });
            } else {
              res.json({success:false, message:err2});
            }
          });
          connection.release();
        } else {
          res.json({success:false, message:err});
        }
      });
    }
  });
});

router.post('/api/makeComment', function(req, res){
  console.log('API : post makeComment');
  console.log(req.body.questionID);
  console.log(req.body.userID);
  console.log(req.body.content);
  pool.getConnection(function(error, connection) {
    if(error){
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('insert into comment(questionID, userID, content) values(?,?,?)',[req.body.questionID, req.body.userID, req.body.content], function(error, result) {
        console.log(result);
        if(error !=null){
          console.log('insert query err: ', error);
          res.json({success:false});
        } else{
          res.json({success: true});
        }
      });
      connection.release();
    }
  });
});

router.get('/api/commentList/:questionID', function(req,res){
  console.log('API: get commentList');
  console.log(req.params.questionID);
  pool.getConnection(function(error, connection){
    if(error){
      console.log('db connection failed: '+error);
      res.sendStatus(503);
    } else {
      connection.query('select * from comment where questionID=?', [req.params.questionID], function(error, result){
        console.log(result);
        if(error == null){
          res.json({
            success : true,
            count : result.length,
            questioninfos : result
          });
        } else {
          res.json({success:false});
        }
      });
      connection.release();
    }
  });
});

module.exports = router;
<commit_msg>공감하기 api 구현<commit_after>var express = require('express');
var router = express.Router();
const fs = require('fs');
const ejs = require('ejs');
const pool = require('../config/db_pool');
// var aws = require('aws-sdk');
// aws.config.loadFromPath('./config/aws_config.json');

/* GET home page. */
// router.get('/', function(req, res, next) {
//     res.render('index');
// });

router.get('/', function(req, res, next) {
  fs.readFile('./views/index.ejs', 'utf-8', function(err, result) {
    if (err)
    console.log('reading ejs error : ', err);
    else
    res.status(200).send(ejs.render(result));
  });
});

router.post('/classConfirm', function(req, res) {

  function myRandom(max, min) {
    return Math.floor(Math.random()*(max-min)) + min;
  }
  var rand = myRandom(1000,9999);

  console.log('연결');
  req.session.className = req.body.className + rand;
  className=req.session.className;
  likeMinimum = req.body.likeMinimum;
  if (likeMinimum.length <= 0)
  likeMinimum = 0;

  console.log(className);
  console.log(likeMinimum);

  pool.getConnection(function(error, connection){
    console.log('들어왔어요');
    if (error)
    {
      console.log("getConnection Error" + error);
      res.sendStatus(503);
    }

    else {
      connection.query('select count(*) as exist from class where className = ?', [className], function(error, row)
      {
        if(error)
        {
          console.log("getConnection Error" + error);
          res.sendStatus(503);
        }
        else
        {
          if(row[0].exist != 0)
          {
            console.log('현존하는 강의가 존재합니다.');
            res.render('index');
          }

          else
          {
            connection.query('insert into class values(null,?,?,?)', [rand, className, likeMinimum], function(error, comments)
            {
              console.log(comments);
              if(error) console.log('selecting query err: ', error);
              else{
                res.render('className', {className : req.session.className});
                connection.release();
              }
            });
          }
        }
      });
    }
  });
});

router.get('/questionList/:className', function(req, res){
  pool.getConnection(function(error, connection){
    console.log('들어왔어요');
    if (error)
    {
      console.log("getConnection Error" + error);
      res.sendStatus(503);
      //connection.release();
    } else {
      connection.query('select id from class where className =?', req.session.className, function(error, row)
      {
        if(error) console.log("selecting Error" + error);
        else {
          console.log(row[0].id);
          req.session.classID = row[0].id;
          connection.query('select id, classID, userID, content, time, commentCnt, likeCnt from question where classID = ?', [row[0].id] , function(error, result) {
            if(error) console.log("selecting Error" + error);
            else
            {
              if(result.length == 0){
                console.log('텅텅 비었어요');
                res.render('realclass', {className : req.session.className, question : null});
              }
              else{
                console.log(result);
                res.render('realClass', {className : req.session.className, question : result});
              }
              connection.release();
            }
          });
        }

      });
    }
  });
});

router.get('/classDetail/:questionID', function(req, res){
  questionID = req.params.questionID;
  className = req.session.className;
  classId = req.session.classID;
  console.log(questionID);
  console.log(classId);
  pool.getConnection(function(error, connection){
    console.log('들어왔어요');
    if (error)
    {
      console.log("getConnection Error" + error);
      res.sendStatus(503);
      //connection.release();
    }

    else {
      connection.query('select * from question where question.id=?', [questionID], function(error, questionData)
      {
        if(error) console.log("selecting Error" + error);
        else {
          console.log('넘어갑니다.');

          connection.query('select * from comment where comment.questionID=?', [questionID], function(error, comments) {
            console.log(questionData);
            console.log(comments);
            res.render('classDetail', {className : req.session.className, questionInfo : questionData, commentsInfo : comments});
          })
        }
      });
    }
    connection.release();
  });
});

router.post('/end', function(req, res){
  req.session.destroy(function(err){
    if(err) console.error('err', err);
    res.redirect('/');
  });
});

/* restful api */
router.post('/api/join', function(req, res){
  console.log(req.body.id);
  console.log(req.body.pw);
  console.log(req.body.email);
  if (req.body.id == null || req.body.pw == null || req.body.email == null) {
    res.json({success:false});
  } else {
    pool.getConnection(function(error, connection){
      if (error)
      {
        console.log("getConnection Error" + error);
        res.sendStatus(503);
      } else {
        connection.query('insert into user values(null, ?, ?, ?)', [req.body.id, req.body.pw, req.body.email], function(err, result) {
          console.log(result);
          if (err != null) {
            console.log('selecting query err: ', err);
            res.json({success:false, message:"중복된 정보가 존재합니다."});
          } else {
            res.json({success:true});
          }
        });
      }
      connection.release();
    });
  }
});

router.post('/api/login', function(req,res){
  console.log(req.body.id);
  console.log(req.body.pw);
  if(req.body.id ==null || req.body.pw==null){
    res.json({sucess:false});
  } else {
    pool.getConnection(function(error, connection){
      if (error)
      {
        console.log("getConnection Error"+ error);
        res.sendStatus(503);
      } else {
        connection.query('select * from user where userID=?',[req.body.id], function(error, result){
          if (error != null) {
            res.json({success:false, message:"Internal Error"});
          } else {
            console.log(result);
            if (result.length <= 0) {
              res.json({success:false, message:"입력하신 ID가 없습니다."});
            } else {
              if (result[0].userPW == req.body.pw) {
                req.session.userID = result[0].id;
                req.session.userName = req.body.id;
                res.json({success:true, message :"로그인 성공", userID:result[0].id});
              } else {
                res.json({success:false, message:"비밀번호가 일치하지 않습니다."});
              }
            }
          }
          connection.release();
        });
      }
    });
  }
});

router.post('/api/logout',function(req, res){
  req.session.destroy(function(){
    req.session;
  });
  res.json({success:true});
});

router.get('/api/classList', function(req, res) {
  console.log('API: get classlist');
  pool.getConnection(function(error, connection) {
    if (error) {
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('select * from class', function(err, result) {
        console.log(result);
        if (err == null) {
          res.json({
            success:true,
            count:result.length,
            classinfos:result
          });
        } else {
          res.json({success:false});
        }
      });
      connection.release();
    }
  });
});

router.post('/api/makeQuestion', function(req, res) {
  console.log('API: post makeQuestion');
  console.log(req.body.classID);
  console.log(req.body.userID);
  console.log(req.body.content);
  pool.getConnection(function(error, connection) {
    if (error) {
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('insert into question (classID, userID, content) values (?, ?, ?)', [req.body.classID, req.body.userID, req.body.content], function(err, result) {
        console.log(result);
        if (err != null) {
          console.log('insert query err: ', err);
          res.json({success:false});
        } else {
          res.json({success:true});
        }
      });
      connection.release();
    }
  });
});

router.get('/api/questionList/:classID', function(req, res) {
  console.log('API: get questinoList');
  console.log(req.params.classID);
  pool.getConnection(function(error, connection) {
    if (error) {
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('select * from question where classID=?', [req.params.classID], function(err, result) {
        console.log(result);
        if (err == null) {
          res.json({
            success:true,
            count:result.length,
            questionInfos:result
          });
        } else {
          res.json({success:false});
        }
      });
      connection.release();
    }
  });
});

router.get('/api/questionDetail/:questionID', function(req, res) {
  console.log('API: get question detail');
  pool.getConnection(function(error, connection) {
    if (error) {
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('select * from question where id=?', [req.params.questionID], function(err, result) {
        if (err == null) {
          console.log(result);
          console.log(req.params.questionID);
          console.log(req.session.userID);
          connection.query('select * from heart where (questionID=? AND userID=?)', [req.params.questionID, req.session.userID], function(err2, result2) {
            if (err2 == null) {
              console.log(result2);
              res.json({
                success:true,
                info:result,
                didLike: (result2.length!=0)
              });
            } else {
              res.json({success:false, message:err2});
            }
          });
          connection.release();
        } else {
          res.json({success:false, message:err});
        }
      });
    }
  });
});

router.put('/api/addLike', function(req, res) {
  console.log('API: put addLike');
  console.log(req.body.questionID);
  console.log(req.body.userID);
  pool.getConnection(function(error, connection) {
    if(error){
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('insert into heart(questionID, userID) values(?,?)',[req.body.questionID, req.body.userID], function(error, result) {
        console.log(result);
        if(error !=null){
          console.log('insert query err: ', error);
          res.json({success:false});
        } else{
          res.json({success: true});
        }
      });
      connection.release();
    }
  });
});

router.delete('/api/removeLike', function(req, res){
  console.log('API : delete remove like');
  console.log(req.body.questionID);
  console.log(req.body.userID);
  pool.getConnection(function(error, connection) {
    if(error){
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('delete from heart where (questionID=? and userID=?)',[req.body.questionID, req.body.userID], function(error, result) {
        console.log(result);
        if(error !=null){
          console.log('insert query err: ', error);
          res.json({success:false});
        } else{
          res.json({success: true});
        }
      });
      connection.release();
    }
  });
});

router.post('/api/makeComment', function(req, res){
  console.log('API : post makeComment');
  console.log(req.body.questionID);
  console.log(req.body.userID);
  console.log(req.body.content);
  pool.getConnection(function(error, connection) {
    if(error){
      console.log('db connection failed: ' + error);
      res.sendStatus(503);
    } else {
      connection.query('insert into comment(questionID, userID, content) values(?,?,?)',[req.body.questionID, req.body.userID, req.body.content], function(error, result) {
        console.log(result);
        if(error !=null){
          console.log('insert query err: ', error);
          res.json({success:false});
        } else{
          res.json({success: true});
        }
      });
      connection.release();
    }
  });
});

router.get('/api/commentList/:questionID', function(req,res){
  console.log('API: get commentList');
  console.log(req.params.questionID);
  pool.getConnection(function(error, connection){
    if(error){
      console.log('db connection failed: '+error);
      res.sendStatus(503);
    } else {
      connection.query('select * from comment where questionID=?', [req.params.questionID], function(error, result){
        console.log(result);
        if(error == null){
          res.json({
            success : true,
            count : result.length,
            questioninfos : result
          });
        } else {
          res.json({success:false});
        }
      });
      connection.release();
    }
  });
});

module.exports = router;
<|endoftext|>


<commit_before>/**
 * React Starter Kit (https://www.reactstarterkit.com/)
 *
 * Copyright © 2014-present Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

/* eslint-disable global-require */

// The top-level (parent) route
export default {

  path: '/',

  // Keep in mind, routes are evaluated in order
  children: [
    require('./page').default,
    // require('./home').default,
    // require('./contact').default,
    // require('./login').default,
    // require('./register').default,
    // require('./about').default,
    // require('./privacy').default,
    // require('./admin').default,
    //
    // // Wildcard routes, e.g. { path: '*', ... } (must go last)
    // require('./notFound').default,
  ],

  async action({ next }) {
    // Execute each child route until one of them return the result
    const route = await next();

    // Provide default values for title, description etc.
    route.title = `${route.title || 'Untitled Page'} - www.reactstarterkit.com`;
    route.description = route.description || '';

    return route;
  },

};
<commit_msg>removed title and description assignment<commit_after>/**
 * React Starter Kit (https://www.reactstarterkit.com/)
 *
 * Copyright © 2014-present Kriasoft, LLC. All rights reserved.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE.txt file in the root directory of this source tree.
 */

/* eslint-disable global-require */

// The top-level (parent) route
export default {

  path: '/',

  // Keep in mind, routes are evaluated in order
  children: [
    require('./page').default,
    // require('./home').default,
    // require('./contact').default,
    // require('./login').default,
    // require('./register').default,
    // require('./about').default,
    // require('./privacy').default,
    // require('./admin').default,
    //
    // // Wildcard routes, e.g. { path: '*', ... } (must go last)
    // require('./notFound').default,
  ],

  async action({ next }) {
    // Execute each child route until one of them return the result
    const route = await next();
    return route;
  },

};
<|endoftext|>


<commit_before>/**
 * Copyright 2018 The Subscribe with Google Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/** @interface */
export class DepsDef {

  /**
   * @return {!../model/doc.Doc}
   */
  doc() {}

  /**
   * @return {!Window}
   */
  win() {}

  /**
   * @return {!../api/subscriptions.Config}
   */
  config() {}

  /**
   * @return {!../model/page-config.PageConfig}
   */
  pageConfig() {}

  /**
   * @return {!web-activities/activity-ports.ActivityPorts}
   */
  activities() {}

  /**
   * @return {!./pay-client.PayClient}
   */
  payClient() {}

  /**
   * @return {!../components/dialog-manager.DialogManager}
   */
  dialogManager() {}

  /**
   * @return {!./entitlements-manager.EntitlementsManager}
   */
  entitlementsManager() {}

  /**
   * @return {!./callbacks.Callbacks}
   */
  callbacks() {}

  /**
   * @return {!../runtime/storage.Storage}
   */
  storage() {}

  /**
   * @return {!../runtime/analytics-service.AnalyticsService}
   */
  analytics() {}

  /**
   * @return {!../runtime/jserror.JsError}
   */
  jserror() {}

  /**
   * @return {!../api/swg-client-event-manager-api.SwgClientEventManagerApi}
   */
  eventManager() {}
}
<commit_msg>remove swg<commit_after>/**
 * Copyright 2018 The Subscribe with Google Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/** @interface */
export class DepsDef {

  /**
   * @return {!../model/doc.Doc}
   */
  doc() {}

  /**
   * @return {!Window}
   */
  win() {}

  /**
   * @return {!../api/subscriptions.Config}
   */
  config() {}

  /**
   * @return {!../model/page-config.PageConfig}
   */
  pageConfig() {}

  /**
   * @return {!web-activities/activity-ports.ActivityPorts}
   */
  activities() {}

  /**
   * @return {!./pay-client.PayClient}
   */
  payClient() {}

  /**
   * @return {!../components/dialog-manager.DialogManager}
   */
  dialogManager() {}

  /**
   * @return {!./entitlements-manager.EntitlementsManager}
   */
  entitlementsManager() {}

  /**
   * @return {!./callbacks.Callbacks}
   */
  callbacks() {}

  /**
   * @return {!../runtime/storage.Storage}
   */
  storage() {}

  /**
   * @return {!../runtime/analytics-service.AnalyticsService}
   */
  analytics() {}

  /**
   * @return {!../runtime/jserror.JsError}
   */
  jserror() {}

  /**
   * @return {!../api/swg-client-event-manager-api.ClientEventManagerApi}
   */
  eventManager() {}
}
<|endoftext|>


<commit_before>import * as PHPType from "./php-type"
import {Identifier, Class, ConstRef} from "./shadowtree"
import * as PHPError from "./php-error"
import * as ClassContext from "./class-context"
import {FileContext} from "./file-context"
import {GlobalContext} from "./global-context"

const fs = require("fs")
const path = require("path")
const zlib = require("zlib")

/**
 * Reads JSON from a compressed PHP info data file
 *
 * @param {string} filename
 * @returns {*}
 */
function readPHPInfo(filename) {
    let compressed = fs.readFileSync(
        __dirname + "/../../data/" + filename
    )
    return JSON.parse(zlib.gunzipSync(compressed).toString("utf8"))
}

/** @type {boolean} If true, this will dump out type info */
const DEBUG_TYPES = false

/** @type {boolean} If true, invalid parent:: references will be ignored */
const IgnoreInvalidParent = true

/**
 * @type {string[]} From ./php-bin/php-constants | gzip > data/php-constants.json.gz
 */
const PHPConstants = readPHPInfo("php-constants.json.gz")

/**
 * @type {{[x: string]: {pbr: boolean[]}}}
 *
 * From: ./php-bin/php-functions | gzip > data/php-functions.json.gz
 */
const PHPFunctions = readPHPInfo("php-functions.json.gz")

/**
 * @type {{[x: string]: string}} From `print json_encode(array_map("gettype",
 * get_defined_vars()), JSON_PRETTY_PRINT);`
 */
const PHPSuperglobals = {
    "_ENV": "array",
    "_GET": "array",
    "_POST": "array",
    "_COOKIE": "array",
    "_FILES": "array",
    "argv": "array",
    "argc": "int",
    "_SERVER": "array",
    "_SESSION": "array",
    "GLOBALS": "array",
}

const PHPFunctionReturnType = {
    array_filter: PHPType.Core.types.array,
    array_key_exists: PHPType.Core.types.bool,
    array_keys: new PHPType.IndexedArray(PHPType.Core.types.string).union,
    array_map: PHPType.Core.types.array,
    array_values: new PHPType.IndexedArray(new PHPType.Mixed(null, "array_values").union).union,
    ceil: PHPType.Core.types.float,
    count: PHPType.Core.types.int,
    floor: PHPType.Core.types.float,
    openssl_decrypt: PHPType.Core.types.string.addTypesFrom(PHPType.Core.types.bool),
    openssl_encrypt: PHPType.Core.types.string.addTypesFrom(PHPType.Core.types.bool),
    preg_replace: PHPType.Core.types.string,
    strcmp: PHPType.Core.types.int,
    stripos: PHPType.Core.types.int.addTypesFrom(PHPType.Core.types.bool),
    strrpos: PHPType.Core.types.int.addTypesFrom(PHPType.Core.types.bool),
    strtolower: PHPType.Core.types.string,
    strtoupper: PHPType.Core.types.string,
    time: PHPType.Core.types.int,
}

/**
 * This defines the entire context applying to the current node.
 */
export default class Context {
    /**
     * @type {{[x: string]: PHPType.Union}} A map of names to types for the
     * "always global" core PHP variables and functions.
     */
    static get superGlobals() {
        if(!this._superGlobals) {
            this._superGlobals = {}
            Object.keys(PHPSuperglobals).forEach(
                name => this._superGlobals["$" + name] =
                    PHPType.Core.named(PHPSuperglobals[name])
            )
            Object.keys(PHPFunctions).forEach(
                name => this._superGlobals[name] = new PHPType.Function(
                    PHPFunctions[name].pbr.map(arg => new PHPType.Mixed(null, name).union),
                    PHPFunctionReturnType[name] || new PHPType.Mixed(null, name).union,
                    PHPFunctions[name].pbr
                ).union
            )
            PHPConstants.forEach(
                name => this._superGlobals[name] = new PHPType.Mixed(null, name).union
            )
            Object.freeze(this._superGlobals)
        }
        return this._superGlobals
    }

    /**
     * Builds the object
     * @param {FileContext} file_context
     * @param {GlobalContext} global_context
     * @param {?ClassContext.Class} [class_context]
     * @param {?{[x: string]: PHPType.Union}} [ns]
     * @param {number} [depth]
     */
    constructor(
        file_context,
        global_context,
        class_context = null,
        ns = null,
        depth = 0
    ) {
        this.classContext = class_context
        this.depth = depth
        this.globalContext = global_context
        this.fileContext = file_context
        /** @type {?PHPType.Union} */
        this.assigningType = null
        this.ns = ns || {}
    }

    /**
     * @type {string[]} All the variable names in the namespace
     */
    get definedVariables() {
        return Object.keys(this.ns).filter(
            name => name.match(/^[$]/)
        ).concat(
            Object.keys(Context.superGlobals).filter(
                name => name.match(/^[$]/)
            )
        )
    }

    /**
     * @type {string}
     */
    get directory() {
        return this.globalContext.workingDirectory
    }

    set directory(v) {
        this.globalContext.workingDirectory = v
    }

    /**
     * @type {?FileContext}
     */
    get fileContext() {
        return this._fileContext
    }

    set fileContext(v) {
        this._fileContext = v
        if(v && !this.directory) {
            let file_directory = v.directory
            let composer_path =
                this.globalContext.findComposerConfig(file_directory)
            if(composer_path) {
                this.directory = path.dirname(composer_path)
            } else {
                this.directory = file_directory
            }
        }
    }

    /**
     * Adds a name to the namespace list.
     * @param {string} name eg. "$foo"
     * @param {PHPType.Union} types
     * @returns {PHPType.Union} The original types
     */
    addName(name, types) {
        if(!this.ns[name]) {
            this.ns[name] = Context.superGlobals[name] || PHPType.Union.empty
        }
        this.ns[name] = this.ns[name].addTypesFrom(types)
        if(DEBUG_TYPES) {
            console.log(`Types for ${name} are: ${this.ns[name]}`)
        }
        return types
    }

    /**
     * Updates which directory the context will work from. Generally only makes
     * sense at the very start of a file.
     *
     * @param {string} directory
     */
    chdir(directory) {
        this.directory = path.resolve(this.directory, directory)
    }

    /**
     * Checks a PHP script
     * @param {string} filename
     * @param {boolean} [with_warnings]
     */
    checkFile(filename, with_warnings = true) {
        let full_filename = path.resolve(this.directory, filename)
        try {
            this.globalContext.checkFile(full_filename, this.depth + 1)
        } catch(e) {
            if(e.errno == -2) {
                if(with_warnings) {
                    console.log(`${full_filename} missing, not installed?`)
                }
            } else {
                throw e
            }
        }
    }

    /**
     * Returns a new context which inherits from this one.
     *
     * @param {boolean} keep_ns If this is true, the same namespace will be
     * linked. Otherwise it will copy non-variables into the new namespace.
     * @returns {Context}
     */
    childContext(keep_ns = false) {
        let ns
        if(keep_ns) {
            ns = this.ns
        } else {
            ns = {}
            Object.keys(this.ns).filter(
                name => name.match(/^\w/)
            ).forEach(name => {
                ns[name] = this.ns[name]
            })
        }
        return new Context(
            this.fileContext,
            this.globalContext,
            this.classContext,
            ns,
            this.depth
        )
    }

    /**
     * Returns the set of types that the given name complies with.
     *
     * @param {string} name
     * @returns {string[]}
     */
    compliantNames(name) {
        if(PHPType.Core.types[name]) {
            return []
        } else {
            let c = this.findClass(name)
            if(c) {
                /** @type {string[]} */
                let types = []
                while(c.superclass) {
                    c = c.superclass
                    types.push(c.name)
                    types = types.concat(c.interfaceNames)
                }
                return types
            } else {
                return []
            }
        }
    }

    /**
     * Finds the class context with the given name
     * @param {string} name Fully qualified only
     * @returns {?ClassContext.Class}
     */
    findClass(name) {
        if(name == "self") {
            return this.classContext
        } else if(
            name != "mixed" &&
            name != "object" &&
            PHPType.Core.types[name]
        ) {
            throw new PHPError.NotClass(`Attempt to access core type ${name} as class`)
        } else {
            return this.globalContext.findClass(name, this.fileContext, this.depth)
        }
    }

    /**
     * If the name is in the namespace, returns its possible types. This only
     * finds variables and functions.
     *
     * @param {string} name eg "$bar"
     * @returns {PHPType.Union}
     */
    findName(name) {
        var types = this.ns[name] || Context.superGlobals[name]
        return types
    }

    /**
     * Finds the trait context with the given name
     *
     * @param {string} name Fully qualified only
     * @returns {?ClassContext.Trait}
     */
    findTrait(name) {
        return this.globalContext.findTrait(name, this.fileContext, this.depth)
    }

    /**
     * Chainable way of setting the class context
     *
     * @param {ClassContext.Class} class_context
     * @returns {this}
     */
    forClass(class_context) {
        this.classContext = class_context
        return this
    }

    /**
     * Copies the namespace from one context to another. The existing namespace
     * will still be retained.
     * @param {Context} context
     */
    importNamespaceFrom(context) {
        Object.keys(context.ns).forEach(
            name => {
                if(this.ns[name]) {
                    this.ns[name] = this.ns[name].addTypesFrom(context.ns[name])
                } else {
                    this.ns[name] = context.ns[name]
                }
            }
        )
    }

    /**
     * Resolves a given name, eg, for "Foo::$bar" this would resolve "Foo". This
     * does not handle multi-node lookups.
     *
     * Variable names are not handled here as they do not require resolution.
     *
     * @param {string} name
     * @param {string} [resolution] eg. "uqn"
     * @returns {string}
     */
    resolveName(name, resolution = "uqn") {
        let md
        if(md = name.match(/^\u005c((\w+)(?:\W.*)?)/)) {
            if(PHPType.Core.types[md[2]]) {
                return md[1]
            } else {
                return name
            }
        }
        switch(resolution) {
            case "fqn":
                return name
            case "uqn":
                if(
                    name == "self" &&
                    this.classContext &&
                    !(this.classContext instanceof ClassContext.Trait)
                ) {
                    return this.classContext.name
                } else if(PHPType.Core.types[name.replace(/\W.*$/, "")]) {
                    return name
                } else if(this.classContext) {
                    let class_name
                    try {
                        class_name = this.classContext.resolveName(name)
                    } catch(e) {
                        if(e instanceof PHPError.Error && IgnoreInvalidParent) {
                            class_name = this.globalContext.addUnknownClass().name
                        } else {
                            throw e
                        }
                    }
                    if(class_name) {
                        return class_name
                    }
                }
                return this.fileContext.resolveName(name)
            case "qn":
                md = name.match(/^(.*)\\(.*)/)
                return `${this.resolveName(md[1])}\\${md[2]}`
            default:
                console.log(name)
                throw new Error(`TODO don't know how to resolve ${resolution}`)
        }
    }

    /**
     * Given that the node has a name, returns its fully resolved form.
     *
     * @param {Identifier|Class|ConstRef} node
     * @returns {string}
     */
    resolveNodeName(node) {
        if(node.name instanceof Identifier) {
            return this.resolveNodeName(node.name)
        } else if(node instanceof Identifier) {
            return this.resolveName(node.name, node.resolution)
        } else if(typeof node.name == "string") {
            return this.resolveName(node.name)
        } else {
            throw new Error(
                `Unable to resolve node name for node ${JSON.stringify(node)}`
            )
        }
    }

    /**
     * Sets the type(s) for a name. This overwrites any types already assigned
     * to the name.
     * @param {string} name
     * @param {PHPType.Union} types
     * @returns {PHPType.Union}
     */
    setName(name, types) {
        this.ns[name] = types
        return types
    }

    /**
     * Sets $this in the scope.
     *
     * @param {?PHPType.Union} [type]
     * @returns {PHPType.Union}
     */
    setThis(type = null) {
        if(this.classContext && !(this.classContext instanceof ClassContext.Trait)) {
            this.ns["$this"] = type || PHPType.Core.named(this.classContext.name)
        } else {
            this.ns["$this"] = type || PHPType.Core.types.self
        }
        return this.ns["$this"]
    }
}<commit_msg>Add another return type<commit_after>import * as PHPType from "./php-type"
import {Identifier, Class, ConstRef} from "./shadowtree"
import * as PHPError from "./php-error"
import * as ClassContext from "./class-context"
import {FileContext} from "./file-context"
import {GlobalContext} from "./global-context"

const fs = require("fs")
const path = require("path")
const zlib = require("zlib")

/**
 * Reads JSON from a compressed PHP info data file
 *
 * @param {string} filename
 * @returns {*}
 */
function readPHPInfo(filename) {
    let compressed = fs.readFileSync(
        __dirname + "/../../data/" + filename
    )
    return JSON.parse(zlib.gunzipSync(compressed).toString("utf8"))
}

/** @type {boolean} If true, this will dump out type info */
const DEBUG_TYPES = false

/** @type {boolean} If true, invalid parent:: references will be ignored */
const IgnoreInvalidParent = true

/**
 * @type {string[]} From ./php-bin/php-constants | gzip > data/php-constants.json.gz
 */
const PHPConstants = readPHPInfo("php-constants.json.gz")

/**
 * @type {{[x: string]: {pbr: boolean[]}}}
 *
 * From: ./php-bin/php-functions | gzip > data/php-functions.json.gz
 */
const PHPFunctions = readPHPInfo("php-functions.json.gz")

/**
 * @type {{[x: string]: string}} From `print json_encode(array_map("gettype",
 * get_defined_vars()), JSON_PRETTY_PRINT);`
 */
const PHPSuperglobals = {
    "_ENV": "array",
    "_GET": "array",
    "_POST": "array",
    "_COOKIE": "array",
    "_FILES": "array",
    "argv": "array",
    "argc": "int",
    "_SERVER": "array",
    "_SESSION": "array",
    "GLOBALS": "array",
}

const PHPFunctionReturnType = {
    array_filter: PHPType.Core.types.array,
    array_key_exists: PHPType.Core.types.bool,
    array_keys: new PHPType.IndexedArray(PHPType.Core.types.string).union,
    array_map: PHPType.Core.types.array,
    array_values: new PHPType.IndexedArray(new PHPType.Mixed(null, "array_values").union).union,
    ceil: PHPType.Core.types.float,
    count: PHPType.Core.types.int,
    floor: PHPType.Core.types.float,
    openssl_decrypt: PHPType.Core.types.string.addTypesFrom(PHPType.Core.types.bool),
    openssl_encrypt: PHPType.Core.types.string.addTypesFrom(PHPType.Core.types.bool),
    preg_replace: PHPType.Core.types.string,
    strcmp: PHPType.Core.types.int,
    stripos: PHPType.Core.types.int.addTypesFrom(PHPType.Core.types.bool),
    strrpos: PHPType.Core.types.int.addTypesFrom(PHPType.Core.types.bool),
    strtolower: PHPType.Core.types.string,
    strtoupper: PHPType.Core.types.string,
    substr: PHPType.Core.types.string,
    time: PHPType.Core.types.int,
}

/**
 * This defines the entire context applying to the current node.
 */
export default class Context {
    /**
     * @type {{[x: string]: PHPType.Union}} A map of names to types for the
     * "always global" core PHP variables and functions.
     */
    static get superGlobals() {
        if(!this._superGlobals) {
            this._superGlobals = {}
            Object.keys(PHPSuperglobals).forEach(
                name => this._superGlobals["$" + name] =
                    PHPType.Core.named(PHPSuperglobals[name])
            )
            Object.keys(PHPFunctions).forEach(
                name => this._superGlobals[name] = new PHPType.Function(
                    PHPFunctions[name].pbr.map(arg => new PHPType.Mixed(null, name).union),
                    PHPFunctionReturnType[name] || new PHPType.Mixed(null, name).union,
                    PHPFunctions[name].pbr
                ).union
            )
            PHPConstants.forEach(
                name => this._superGlobals[name] = new PHPType.Mixed(null, name).union
            )
            Object.freeze(this._superGlobals)
        }
        return this._superGlobals
    }

    /**
     * Builds the object
     * @param {FileContext} file_context
     * @param {GlobalContext} global_context
     * @param {?ClassContext.Class} [class_context]
     * @param {?{[x: string]: PHPType.Union}} [ns]
     * @param {number} [depth]
     */
    constructor(
        file_context,
        global_context,
        class_context = null,
        ns = null,
        depth = 0
    ) {
        this.classContext = class_context
        this.depth = depth
        this.globalContext = global_context
        this.fileContext = file_context
        /** @type {?PHPType.Union} */
        this.assigningType = null
        this.ns = ns || {}
    }

    /**
     * @type {string[]} All the variable names in the namespace
     */
    get definedVariables() {
        return Object.keys(this.ns).filter(
            name => name.match(/^[$]/)
        ).concat(
            Object.keys(Context.superGlobals).filter(
                name => name.match(/^[$]/)
            )
        )
    }

    /**
     * @type {string}
     */
    get directory() {
        return this.globalContext.workingDirectory
    }

    set directory(v) {
        this.globalContext.workingDirectory = v
    }

    /**
     * @type {?FileContext}
     */
    get fileContext() {
        return this._fileContext
    }

    set fileContext(v) {
        this._fileContext = v
        if(v && !this.directory) {
            let file_directory = v.directory
            let composer_path =
                this.globalContext.findComposerConfig(file_directory)
            if(composer_path) {
                this.directory = path.dirname(composer_path)
            } else {
                this.directory = file_directory
            }
        }
    }

    /**
     * Adds a name to the namespace list.
     * @param {string} name eg. "$foo"
     * @param {PHPType.Union} types
     * @returns {PHPType.Union} The original types
     */
    addName(name, types) {
        if(!this.ns[name]) {
            this.ns[name] = Context.superGlobals[name] || PHPType.Union.empty
        }
        this.ns[name] = this.ns[name].addTypesFrom(types)
        if(DEBUG_TYPES) {
            console.log(`Types for ${name} are: ${this.ns[name]}`)
        }
        return types
    }

    /**
     * Updates which directory the context will work from. Generally only makes
     * sense at the very start of a file.
     *
     * @param {string} directory
     */
    chdir(directory) {
        this.directory = path.resolve(this.directory, directory)
    }

    /**
     * Checks a PHP script
     * @param {string} filename
     * @param {boolean} [with_warnings]
     */
    checkFile(filename, with_warnings = true) {
        let full_filename = path.resolve(this.directory, filename)
        try {
            this.globalContext.checkFile(full_filename, this.depth + 1)
        } catch(e) {
            if(e.errno == -2) {
                if(with_warnings) {
                    console.log(`${full_filename} missing, not installed?`)
                }
            } else {
                throw e
            }
        }
    }

    /**
     * Returns a new context which inherits from this one.
     *
     * @param {boolean} keep_ns If this is true, the same namespace will be
     * linked. Otherwise it will copy non-variables into the new namespace.
     * @returns {Context}
     */
    childContext(keep_ns = false) {
        let ns
        if(keep_ns) {
            ns = this.ns
        } else {
            ns = {}
            Object.keys(this.ns).filter(
                name => name.match(/^\w/)
            ).forEach(name => {
                ns[name] = this.ns[name]
            })
        }
        return new Context(
            this.fileContext,
            this.globalContext,
            this.classContext,
            ns,
            this.depth
        )
    }

    /**
     * Returns the set of types that the given name complies with.
     *
     * @param {string} name
     * @returns {string[]}
     */
    compliantNames(name) {
        if(PHPType.Core.types[name]) {
            return []
        } else {
            let c = this.findClass(name)
            if(c) {
                /** @type {string[]} */
                let types = []
                while(c.superclass) {
                    c = c.superclass
                    types.push(c.name)
                    types = types.concat(c.interfaceNames)
                }
                return types
            } else {
                return []
            }
        }
    }

    /**
     * Finds the class context with the given name
     * @param {string} name Fully qualified only
     * @returns {?ClassContext.Class}
     */
    findClass(name) {
        if(name == "self") {
            return this.classContext
        } else if(
            name != "mixed" &&
            name != "object" &&
            PHPType.Core.types[name]
        ) {
            throw new PHPError.NotClass(`Attempt to access core type ${name} as class`)
        } else {
            return this.globalContext.findClass(name, this.fileContext, this.depth)
        }
    }

    /**
     * If the name is in the namespace, returns its possible types. This only
     * finds variables and functions.
     *
     * @param {string} name eg "$bar"
     * @returns {PHPType.Union}
     */
    findName(name) {
        var types = this.ns[name] || Context.superGlobals[name]
        return types
    }

    /**
     * Finds the trait context with the given name
     *
     * @param {string} name Fully qualified only
     * @returns {?ClassContext.Trait}
     */
    findTrait(name) {
        return this.globalContext.findTrait(name, this.fileContext, this.depth)
    }

    /**
     * Chainable way of setting the class context
     *
     * @param {ClassContext.Class} class_context
     * @returns {this}
     */
    forClass(class_context) {
        this.classContext = class_context
        return this
    }

    /**
     * Copies the namespace from one context to another. The existing namespace
     * will still be retained.
     * @param {Context} context
     */
    importNamespaceFrom(context) {
        Object.keys(context.ns).forEach(
            name => {
                if(this.ns[name]) {
                    this.ns[name] = this.ns[name].addTypesFrom(context.ns[name])
                } else {
                    this.ns[name] = context.ns[name]
                }
            }
        )
    }

    /**
     * Resolves a given name, eg, for "Foo::$bar" this would resolve "Foo". This
     * does not handle multi-node lookups.
     *
     * Variable names are not handled here as they do not require resolution.
     *
     * @param {string} name
     * @param {string} [resolution] eg. "uqn"
     * @returns {string}
     */
    resolveName(name, resolution = "uqn") {
        let md
        if(md = name.match(/^\u005c((\w+)(?:\W.*)?)/)) {
            if(PHPType.Core.types[md[2]]) {
                return md[1]
            } else {
                return name
            }
        }
        switch(resolution) {
            case "fqn":
                return name
            case "uqn":
                if(
                    name == "self" &&
                    this.classContext &&
                    !(this.classContext instanceof ClassContext.Trait)
                ) {
                    return this.classContext.name
                } else if(PHPType.Core.types[name.replace(/\W.*$/, "")]) {
                    return name
                } else if(this.classContext) {
                    let class_name
                    try {
                        class_name = this.classContext.resolveName(name)
                    } catch(e) {
                        if(e instanceof PHPError.Error && IgnoreInvalidParent) {
                            class_name = this.globalContext.addUnknownClass().name
                        } else {
                            throw e
                        }
                    }
                    if(class_name) {
                        return class_name
                    }
                }
                return this.fileContext.resolveName(name)
            case "qn":
                md = name.match(/^(.*)\\(.*)/)
                return `${this.resolveName(md[1])}\\${md[2]}`
            default:
                console.log(name)
                throw new Error(`TODO don't know how to resolve ${resolution}`)
        }
    }

    /**
     * Given that the node has a name, returns its fully resolved form.
     *
     * @param {Identifier|Class|ConstRef} node
     * @returns {string}
     */
    resolveNodeName(node) {
        if(node.name instanceof Identifier) {
            return this.resolveNodeName(node.name)
        } else if(node instanceof Identifier) {
            return this.resolveName(node.name, node.resolution)
        } else if(typeof node.name == "string") {
            return this.resolveName(node.name)
        } else {
            throw new Error(
                `Unable to resolve node name for node ${JSON.stringify(node)}`
            )
        }
    }

    /**
     * Sets the type(s) for a name. This overwrites any types already assigned
     * to the name.
     * @param {string} name
     * @param {PHPType.Union} types
     * @returns {PHPType.Union}
     */
    setName(name, types) {
        this.ns[name] = types
        return types
    }

    /**
     * Sets $this in the scope.
     *
     * @param {?PHPType.Union} [type]
     * @returns {PHPType.Union}
     */
    setThis(type = null) {
        if(this.classContext && !(this.classContext instanceof ClassContext.Trait)) {
            this.ns["$this"] = type || PHPType.Core.named(this.classContext.name)
        } else {
            this.ns["$this"] = type || PHPType.Core.types.self
        }
        return this.ns["$this"]
    }
}<|endoftext|>


<commit_before>// https://github.com/spotify/web-api/issues/321

import request from 'request';

class Spotify {

    static playbackCommands = ['next', 'previous', 'volume', 'shuffle', 'repeat', 'play', 'pause', 'seek'];
    /*static browseTypes = {
        featured: ,
        new: ,
        categories: ,
        charts:
    };*/

    constructor(tokenData, userData) {
        this.tokenData = tokenData;
        this.userData = userData;
    }

    static requestOk(response) {
        return response.statusCode === 200 || response.statusCode === 201 || response.statusCode === 204;
    }

    static initToken(authCode) {
        return new Promise((resolve, reject) => {

            const options = {
                url: 'https://accounts.spotify.com/api/token',
                    headers: {
                    'Authorization': 'Basic ' + (Buffer.from(process.env.SPOTIFY_CLIENT_ID + ':' + process.env.SPOTIFY_CLIENT_SECRET).toString('base64'))
                },
                form: {
                    'grant_type': 'authorization_code',
                    'code': authCode,
                    'redirect_uri': process.env.SPOTIFY_REDIRECT_URI
                },
                json: true
            };

            request.post(options, (error, response, body) => {
                if (!error && Spotify.requestOk(response)) {
                    resolve(Spotify.createToken(body));
                } else {
                    reject(response.statusCode)
                }
            });
        });
    }

    static createToken(body) {
        return {
            refreshToken: body.refresh_token,
            accessToken: body.access_token,
            expiry: (Date.now() / 1000) + body.expires_in
        }
    }

    getAccessToken() {
        return new Promise((resolve, reject) => {
            if (!this.tokenData) {
                reject('authorization code not valid');
                return;
            }

            // check if access token has expired - refresh if it does
            if (Date.now() / 1000 >= this.tokenData.expiry) {
                const options = {
                    url: 'https://accounts.spotify.com/api/token',
                        headers: {
                        'Authorization': 'Basic ' + (Buffer.from(process.env.SPOTIFY_CLIENT_ID + ':' + process.env.SPOTIFY_CLIENT_SECRET).toString('base64'))
                    },
                    form: {
                        'grant_type': 'refresh_token',
                        'refresh_token': this.tokenData.refreshToken

                    },
                    json: true
                };

                request.post(options, (error, response, body) => {
                    if (!error && Spotify.requestOk(response)) {
                        this.tokenData = Spotify.createToken(body);
                        resolve(body.access_token);
                    } else {
                        reject(response.statusCode)
                    }
                });

            } else {
                resolve(this.tokenData.accessToken);
                return;
            }
        })
    }

    post(endPoint, data) {
        return new Promise(async (resolve, reject) => {
            try {
                var token = await this.getAccessToken();
                var options = {
                    url: 'https://api.spotify.com/v1' + endPoint,
                    headers: { 'Authorization': 'Bearer ' + token },
                    json: true
                };

                if (data) options.body = data;

                console.log('POST: ' + endPoint);

                request.post(options, function(error, response, body) {
                    if (!error && Spotify.requestOk(response)) {
                        resolve(body || true);
                    } else {
                        console.log(body);
                        reject(response.statusCode);
                    }
                });
            } catch (err) {
                reject(err);
            }
        });
    }

    put(endPoint, data) {
        return new Promise(async (resolve, reject) => {
            try {
                var token = await this.getAccessToken();
                var options = {
                    url: 'https://api.spotify.com/v1' + endPoint,
                    headers: { 'Authorization': 'Bearer ' + token },
                    json: true
                };

                if (data) options.body = data;

                console.log('PUT: ' + endPoint);

                request.put(options, function(error, response, body) {
                    if (!error && Spotify.requestOk(response)) {
                        resolve(body || true);
                    } else {
                        console.log(body);
                        reject(response.statusCode);
                    }
                });
            } catch (err) {
                reject(err);
            }
        });
    }

    delete(endPoint, data) {
        return new Promise(async (resolve, reject) => {
            try {
                var token = await this.getAccessToken();
                var options = {
                    url: 'https://api.spotify.com/v1' + endPoint,
                    headers: { 'Authorization': 'Bearer ' + token },
                    json: true
                };

                if (data) options.body = data;

                console.log('DELETE: ' + endPoint);

                request.delete(options, function(error, response, body) {
                    if (!error && Spotify.requestOk(response)) {
                        resolve(body || true);
                    } else {
                        console.log(body);
                        reject(response.statusCode);
                    }
                });
            } catch (err) {
                reject(err);
            }
        });
    }

    get(endPoint, data = null) {
        return new Promise(async (resolve, reject) => {
            try {
                var token = await this.getAccessToken();
                var options = {
                    url: 'https://api.spotify.com/v1' + endPoint,
                    headers: {
                        'Authorization': 'Bearer ' + token
                    },
                    json: true
                };

                if (data) options.qs = data;

                console.log('GET: ' + endPoint);

                request.get(options, function(error, response, body) {
                    if (!error && Spotify.requestOk(response)) {
                        resolve(body || true);
                    } else {
                        console.log(body);
                        reject(response.statusCode);
                    }
                });
            } catch (err) {
                reject(err);
            }
        });
    }

    async init() {
        try {
            var userData = await this.getUserData();

            var botPlaylist = null;
            var playlistData = await this.getPlaylists();

            if (playlistData) {
                playlistData.forEach((playlist) => {
                    if (playlist.owner.id === userData.id && playlist.name === process.env.SPOTIFY_QUEUE_PLAYLIST_NAME) {
                        botPlaylist = playlist;
                    }
                })
            }

            return { userData, playlist: botPlaylist };
        } catch (err) {
            console.log(err);
        }
    }

    async getUserData() {
        try {
            return await this.get('/me');
        } catch (err) {
            console.log(err);
        }
    }

    async search(query, options) {
        var queryOptions = Object.assign({ type: 'track', limit: 10, market: 'from_token' }, options);

        try {
            console.log('searching for "' + query + '"');
            const data = await this.get('/search', {
                q: query,
                ...queryOptions
            });

            if (data && data[queryOptions.type + 's'].items.length > 0) {
                return data[queryOptions.type + 's'].items;
            } else return null;

        } catch (err) {
            console.log(err)
        }
    }

    async play(uri = null, deviceId = null, playlistUri = null) {
        try {
            var options = null;
            if (uri || playlistUri) {
                options = {};

                // if (uri) options.uris = [ uri ];
                if (playlistUri) options.context_uri = playlistUri;
                if (uri) options.offset = { uri };
            }

            return await this.put('/me/player/play' + (deviceId ? '?device_id=' + deviceId : ''), options);
        } catch (err) {
            console.log(err);
        }
    }

    async getDevices() {
        try {
            var data = await this.get('/me/player/devices');
            return data && data.devices;
        } catch (err) {
            console.log(err);
        }
    }

    async setDevice(deviceId) {
        try {
            return await this.put('/me/player', {
                device_ids: [ deviceId ]
            });
        } catch (err) {
            console.log(err);
        }
    }

    async getCurrentTrack() {
        try {
            var data = await this.get('/me/player/currently-playing');
            return data && data.item;
        } catch (err) {
            console.log(err)
        }
    }

    async getCurrentPlayback() {
        try {
            return await this.get('/me/player');
        } catch (err) {
            console.log(err);
        }
    }

    async getBrowseCategories() {
        try {
            var data = await this.get('/browse/categories', {
                country: this.userData.country
            });

            return data && data.categories.items;
        } catch (err) {
            console.log(err);
        }
    }

    async browsePlaylists(type = 'featured-playlists', options) {
        try {
            var data = null;

            switch (type) {
                case 'search':
                    if (options && options.query) {
                        data = await this.search(options.query, {
                            type: 'playlist',
                            limit: 20
                        });
                    }

                    break;
                case 'user-playlists':
                    data = await this.getPlaylists();
                    break;
                case 'featured-playlists':
                    var featuredPlaylists = await this.get('/browse/' + type, {
                        country: this.userData.country
                    });

                    if (featuredPlaylists) {
                        data = featuredPlaylists.playlists.items;
                    }

                    break;
                case 'categories':
                    if (options && options.categoryId) {
                        var categoryPlaylists = await this.get('/browse/categories/' + options.categoryId + '/playlists', {
                            country: this.userData.country
                        });

                        if (categoryPlaylists) {
                            data = categoryPlaylists.playlists.items;
                        }
                    }

                    break;
                case 'charts':
                    var chartPlaylists = ['37i9dQZEVXbMDoHDwVN2tF', '37i9dQZEVXbLiRSasKsNU9'];
                    var playlists = [];

                    for (var i in chartPlaylists) {
                        var playlistId = chartPlaylists[i];
                        var playlistData = await this.get('/users/spotifycharts/playlists/' + playlistId, {
                            market: this.userData.country,
                            fields: 'collaborative, description, external_urls, followers, href, id, images, name, tracks(!items), owner, public, snapshot_id, type, uri'
                        });

                        playlists.push(playlistData);
                    }

                    data = playlists;
                    break;
                case 'search':
                    // search playlists here
                    break;
            }

            return data;
        } catch (err) {
            console.log(err);
        }
    }

    async getPlaylists() {
        try {
            var data = await this.get('/me/playlists');
            return data && data.items;
        } catch (err) {
            console.log(err)
        }
    }

    async createPlaylist(name) {
        try {
            return await this.post('/users/'+this.userData.id+'/playlists', {
                description: 'playlist created by bot',
                public: false,
                name
            });
        } catch (err) {
            console.log(err)
        }
    }

    async getPlaylistTracks(ownerId, playlistId) {
        try {
            var data = await this.get('/users/'+ownerId+'/playlists/'+playlistId+'/tracks', {
                market: this.userData.country,
                fields: 'items(track(id, artists, external_urls, uri, name, album(id, name, images)))'
            });

            return data && data.items.map(item => item.track);
        } catch (err) {
            console.log(err);
        }
    }

    async addTrackToPlaylist(uri, playlistId) {
        try {
            var currentTracks = await this.getPlaylistTracks(this.userData.id, playlistId);
            for (var i in currentTracks) {
                var track = currentTracks[i];
                if (track.uri === uri) {
                    console.log('addTrackToPlaylist: existing track');
                    return true;
                }
            }

            return await this.post('/users/'+this.userData.id+'/playlists/'+playlistId+'/tracks', {
                uris: [ uri ]
            });
        } catch (err) {
            console.log(err);
        }
    }

    async clearPlaylist(playlistId) {
        try {
            var tracksData = await this.getPlaylistTracks(this.userData.id, playlistId);
            if (tracksData) {
                var tracks = tracksData.map(track => {
                    return { uri: track.uri }
                });

                return await this.delete('/users/'+this.userData.id+'/playlists/'+playlistId+'/tracks', {
                    tracks
                });

            } else return false;
        } catch (err) {
            console.log(err);
        }
    }

    async playback(args, deviceId = null, callback) {
        try {
            var result = false;
            if (args && args.command) {
                var track = await this.getCurrentTrack();
                var playback = await this.getCurrentPlayback();

                if (track) {
                    var trackDuration = track.duration_ms;
                    var deviceIdParam = deviceId ? '?device_id=' + deviceId : '';
                    switch (args.command) {
                        case 'play':
                        case 'pause':
                            result = await this.put('/me/player/' + args.command + deviceIdParam);
                            if (result) {
                                callback('okay');
                            }

                            break;
                        case 'previous':
                        case 'next':
                            result = await this.post('/me/player/' + args.command + deviceIdParam);
                            if (result) {
                                await this.put('/me/player/play');
                                setTimeout(async () => {
                                    var track = await this.getCurrentTrack();
                                    callback('now playing **' + track.artists[0].name + ' - ' + track.name + '**');
                                }, 2000)
                            }

                            break;
                        case 'seek':
                            var duration = null;
                            if (args.time) {
                                duration = args.time.split(':').reverse().reduce((prev, curr, i) => prev + curr * Math.pow(60, i), 0) * 1000;
                            } else if (args.number) {
                                percent = parseInt(args.number.trim()) / 100;
                                duration = trackDuration * percent;
                            }

                            if (duration) {
                                result = await this.put('/me/player/seek' + deviceIdParam + (deviceIdParam ? '&' : '?') + 'position_ms=' + Math.min(duration, trackDuration));
                                if (result) {
                                    callback('seeked to ' + (( duration / 1000 / 60 )) + ' mins');
                                }
                            }

                            break;
                        case 'repeat':
                            if (playback) {
                                var state = 'off';
                                var states = ['off', 'track', 'context'];

                                if (args.switchOff) {
                                    state = 'off';
                                } else {
                                    var currentState = playback.repeat_state;
                                    var stateIndex = states.indexOf(currentState);
                                    if (stateIndex + 1 > states.length) {
                                        stateIndex = 0;
                                    }

                                    state = states[stateIndex + 1];
                                }

                                result = await this.put('/me/player/repeat?state=' + state);
                                if (result) {
                                    callback('repeat is now **'+state+'**');
                                }
                            }

                            break;
                        case 'shuffle':
                            if (playback) {
                                var state = null;

                                if (args.switchOn) {
                                    state = 'true';
                                } else if (args.switchOff) {
                                    state = 'false';
                                } else {
                                    var currentShuffleState = playback.shuffle_state;
                                    state = !currentShuffleState ? 'true' : 'false';
                                }

                                result = await this.put('/me/player/shuffle?state=' + state);
                                if (result) {
                                    callback(state === 'true' ? 'on' : 'off');
                                }
                            }

                            break;
                        case 'volume':
                            var percent = 0;
                            if (args.number) {
                                percent = Math.min(parseFloat(args.number.trim()), 100);
                            }

                            result = await this.put('/me/player/volume?volume_percent=' + percent);
                            if (result) {
                                callback('volume set to **' + percent + '%**');
                            }

                            break;
                    }
                }
            }

            return result;
        } catch (err) {
            console.log(err);
        }
    }
}

export default Spotify;
<commit_msg>clean up<commit_after>// https://github.com/spotify/web-api/issues/321

import request from 'request';

class Spotify {

    static playbackCommands = ['next', 'previous', 'volume', 'shuffle', 'repeat', 'play', 'pause', 'seek'];

    constructor(tokenData, userData) {
        this.tokenData = tokenData;
        this.userData = userData;
    }

    static requestOk(response) {
        return response.statusCode === 200 || response.statusCode === 201 || response.statusCode === 204;
    }

    static initToken(authCode) {
        return new Promise((resolve, reject) => {

            const options = {
                url: 'https://accounts.spotify.com/api/token',
                    headers: {
                    'Authorization': 'Basic ' + (Buffer.from(process.env.SPOTIFY_CLIENT_ID + ':' + process.env.SPOTIFY_CLIENT_SECRET).toString('base64'))
                },
                form: {
                    'grant_type': 'authorization_code',
                    'code': authCode,
                    'redirect_uri': process.env.SPOTIFY_REDIRECT_URI
                },
                json: true
            };

            request.post(options, (error, response, body) => {
                if (!error && Spotify.requestOk(response)) {
                    resolve(Spotify.createToken(body));
                } else {
                    reject(response.statusCode)
                }
            });
        });
    }

    static createToken(body) {
        return {
            refreshToken: body.refresh_token,
            accessToken: body.access_token,
            expiry: (Date.now() / 1000) + body.expires_in
        }
    }

    getAccessToken() {
        return new Promise((resolve, reject) => {
            if (!this.tokenData) {
                reject('authorization code not valid');
                return;
            }

            // check if access token has expired - refresh if it does
            if (Date.now() / 1000 >= this.tokenData.expiry) {
                const options = {
                    url: 'https://accounts.spotify.com/api/token',
                        headers: {
                        'Authorization': 'Basic ' + (Buffer.from(process.env.SPOTIFY_CLIENT_ID + ':' + process.env.SPOTIFY_CLIENT_SECRET).toString('base64'))
                    },
                    form: {
                        'grant_type': 'refresh_token',
                        'refresh_token': this.tokenData.refreshToken

                    },
                    json: true
                };

                request.post(options, (error, response, body) => {
                    if (!error && Spotify.requestOk(response)) {
                        this.tokenData = Spotify.createToken(body);
                        resolve(body.access_token);
                    } else {
                        reject(response.statusCode)
                    }
                });

            } else {
                resolve(this.tokenData.accessToken);
                return;
            }
        })
    }

    post(endPoint, data) {
        return new Promise(async (resolve, reject) => {
            try {
                var token = await this.getAccessToken();
                var options = {
                    url: 'https://api.spotify.com/v1' + endPoint,
                    headers: { 'Authorization': 'Bearer ' + token },
                    json: true
                };

                if (data) options.body = data;

                console.log('POST: ' + endPoint);

                request.post(options, function(error, response, body) {
                    if (!error && Spotify.requestOk(response)) {
                        resolve(body || true);
                    } else {
                        console.log(body);
                        reject(response.statusCode);
                    }
                });
            } catch (err) {
                reject(err);
            }
        });
    }

    put(endPoint, data) {
        return new Promise(async (resolve, reject) => {
            try {
                var token = await this.getAccessToken();
                var options = {
                    url: 'https://api.spotify.com/v1' + endPoint,
                    headers: { 'Authorization': 'Bearer ' + token },
                    json: true
                };

                if (data) options.body = data;

                console.log('PUT: ' + endPoint);

                request.put(options, function(error, response, body) {
                    if (!error && Spotify.requestOk(response)) {
                        resolve(body || true);
                    } else {
                        console.log(body);
                        reject(response.statusCode);
                    }
                });
            } catch (err) {
                reject(err);
            }
        });
    }

    delete(endPoint, data) {
        return new Promise(async (resolve, reject) => {
            try {
                var token = await this.getAccessToken();
                var options = {
                    url: 'https://api.spotify.com/v1' + endPoint,
                    headers: { 'Authorization': 'Bearer ' + token },
                    json: true
                };

                if (data) options.body = data;

                console.log('DELETE: ' + endPoint);

                request.delete(options, function(error, response, body) {
                    if (!error && Spotify.requestOk(response)) {
                        resolve(body || true);
                    } else {
                        console.log(body);
                        reject(response.statusCode);
                    }
                });
            } catch (err) {
                reject(err);
            }
        });
    }

    get(endPoint, data = null) {
        return new Promise(async (resolve, reject) => {
            try {
                var token = await this.getAccessToken();
                var options = {
                    url: 'https://api.spotify.com/v1' + endPoint,
                    headers: {
                        'Authorization': 'Bearer ' + token
                    },
                    json: true
                };

                if (data) options.qs = data;

                console.log('GET: ' + endPoint);

                request.get(options, function(error, response, body) {
                    if (!error && Spotify.requestOk(response)) {
                        resolve(body || true);
                    } else {
                        console.log(body);
                        reject(response.statusCode);
                    }
                });
            } catch (err) {
                reject(err);
            }
        });
    }

    async init() {
        try {
            var userData = await this.getUserData();

            var botPlaylist = null;
            var playlistData = await this.getPlaylists();

            if (playlistData) {
                playlistData.forEach((playlist) => {
                    if (playlist.owner.id === userData.id && playlist.name === process.env.SPOTIFY_QUEUE_PLAYLIST_NAME) {
                        botPlaylist = playlist;
                    }
                })
            }

            return { userData, playlist: botPlaylist };
        } catch (err) {
            console.log(err);
        }
    }

    async getUserData() {
        try {
            return await this.get('/me');
        } catch (err) {
            console.log(err);
        }
    }

    async search(query, options) {
        var queryOptions = Object.assign({ type: 'track', limit: 10, market: 'from_token' }, options);

        try {
            console.log('searching for "' + query + '"');
            const data = await this.get('/search', {
                q: query,
                ...queryOptions
            });

            if (data && data[queryOptions.type + 's'].items.length > 0) {
                return data[queryOptions.type + 's'].items;
            } else return null;

        } catch (err) {
            console.log(err)
        }
    }

    async play(uri = null, deviceId = null, playlistUri = null) {
        try {
            var options = null;
            if (uri || playlistUri) {
                options = {};

                // if (uri) options.uris = [ uri ];
                if (playlistUri) options.context_uri = playlistUri;
                if (uri) options.offset = { uri };
            }

            return await this.put('/me/player/play' + (deviceId ? '?device_id=' + deviceId : ''), options);
        } catch (err) {
            console.log(err);
        }
    }

    async getDevices() {
        try {
            var data = await this.get('/me/player/devices');
            return data && data.devices;
        } catch (err) {
            console.log(err);
        }
    }

    async setDevice(deviceId) {
        try {
            return await this.put('/me/player', {
                device_ids: [ deviceId ]
            });
        } catch (err) {
            console.log(err);
        }
    }

    async getCurrentTrack() {
        try {
            var data = await this.get('/me/player/currently-playing');
            return data && data.item;
        } catch (err) {
            console.log(err)
        }
    }

    async getCurrentPlayback() {
        try {
            return await this.get('/me/player');
        } catch (err) {
            console.log(err);
        }
    }

    async getBrowseCategories() {
        try {
            var data = await this.get('/browse/categories', {
                country: this.userData.country
            });

            return data && data.categories.items;
        } catch (err) {
            console.log(err);
        }
    }

    async browsePlaylists(type = 'featured-playlists', options) {
        try {
            var data = null;

            switch (type) {
                case 'search':
                    if (options && options.query) {
                        data = await this.search(options.query, {
                            type: 'playlist',
                            limit: 20
                        });
                    }

                    break;
                case 'user-playlists':
                    data = await this.getPlaylists();
                    break;
                case 'featured-playlists':
                    var featuredPlaylists = await this.get('/browse/' + type, {
                        country: this.userData.country
                    });

                    if (featuredPlaylists) {
                        data = featuredPlaylists.playlists.items;
                    }

                    break;
                case 'categories':
                    if (options && options.categoryId) {
                        var categoryPlaylists = await this.get('/browse/categories/' + options.categoryId + '/playlists', {
                            country: this.userData.country
                        });

                        if (categoryPlaylists) {
                            data = categoryPlaylists.playlists.items;
                        }
                    }

                    break;
                case 'charts':
                    var chartPlaylists = ['37i9dQZEVXbMDoHDwVN2tF', '37i9dQZEVXbLiRSasKsNU9'];
                    var playlists = [];

                    for (var i in chartPlaylists) {
                        var playlistId = chartPlaylists[i];
                        var playlistData = await this.get('/users/spotifycharts/playlists/' + playlistId, {
                            market: this.userData.country,
                            fields: 'collaborative, description, external_urls, followers, href, id, images, name, tracks(!items), owner, public, snapshot_id, type, uri'
                        });

                        playlists.push(playlistData);
                    }

                    data = playlists;
                    break;
                case 'search':
                    // search playlists here
                    break;
            }

            return data;
        } catch (err) {
            console.log(err);
        }
    }

    async getPlaylists() {
        try {
            var data = await this.get('/me/playlists');
            return data && data.items;
        } catch (err) {
            console.log(err)
        }
    }

    async createPlaylist(name) {
        try {
            return await this.post('/users/'+this.userData.id+'/playlists', {
                description: 'playlist created by bot',
                public: false,
                name
            });
        } catch (err) {
            console.log(err)
        }
    }

    async getPlaylistTracks(ownerId, playlistId) {
        try {
            var data = await this.get('/users/'+ownerId+'/playlists/'+playlistId+'/tracks', {
                market: this.userData.country,
                fields: 'items(track(id, artists, external_urls, uri, name, album(id, name, images)))'
            });

            return data && data.items.map(item => item.track);
        } catch (err) {
            console.log(err);
        }
    }

    async addTrackToPlaylist(uri, playlistId) {
        try {
            var currentTracks = await this.getPlaylistTracks(this.userData.id, playlistId);
            for (var i in currentTracks) {
                var track = currentTracks[i];
                if (track.uri === uri) {
                    console.log('addTrackToPlaylist: existing track');
                    return true;
                }
            }

            return await this.post('/users/'+this.userData.id+'/playlists/'+playlistId+'/tracks', {
                uris: [ uri ]
            });
        } catch (err) {
            console.log(err);
        }
    }

    async clearPlaylist(playlistId) {
        try {
            var tracksData = await this.getPlaylistTracks(this.userData.id, playlistId);
            if (tracksData) {
                var tracks = tracksData.map(track => {
                    return { uri: track.uri }
                });

                return await this.delete('/users/'+this.userData.id+'/playlists/'+playlistId+'/tracks', {
                    tracks
                });

            } else return false;
        } catch (err) {
            console.log(err);
        }
    }

    async playback(args, deviceId = null, callback) {
        try {
            var result = false;
            if (args && args.command) {
                var track = await this.getCurrentTrack();
                var playback = await this.getCurrentPlayback();

                if (track) {
                    var trackDuration = track.duration_ms;
                    var deviceIdParam = deviceId ? '?device_id=' + deviceId : '';
                    switch (args.command) {
                        case 'play':
                        case 'pause':
                            result = await this.put('/me/player/' + args.command + deviceIdParam);
                            if (result) {
                                callback('okay');
                            }

                            break;
                        case 'previous':
                        case 'next':
                            result = await this.post('/me/player/' + args.command + deviceIdParam);
                            if (result) {
                                await this.put('/me/player/play');
                                setTimeout(async () => {
                                    var track = await this.getCurrentTrack();
                                    callback('now playing **' + track.artists[0].name + ' - ' + track.name + '**');
                                }, 2000)
                            }

                            break;
                        case 'seek':
                            var duration = null;
                            if (args.time) {
                                duration = args.time.split(':').reverse().reduce((prev, curr, i) => prev + curr * Math.pow(60, i), 0) * 1000;
                            } else if (args.number) {
                                percent = parseInt(args.number.trim()) / 100;
                                duration = trackDuration * percent;
                            }

                            if (duration) {
                                result = await this.put('/me/player/seek' + deviceIdParam + (deviceIdParam ? '&' : '?') + 'position_ms=' + Math.min(duration, trackDuration));
                                if (result) {
                                    callback('seeked to ' + (( duration / 1000 / 60 )) + ' mins');
                                }
                            }

                            break;
                        case 'repeat':
                            if (playback) {
                                var state = 'off';
                                var states = ['off', 'track', 'context'];

                                if (args.switchOff) {
                                    state = 'off';
                                } else {
                                    var currentState = playback.repeat_state;
                                    var stateIndex = states.indexOf(currentState);
                                    if (stateIndex + 1 > states.length) {
                                        stateIndex = 0;
                                    }

                                    state = states[stateIndex + 1];
                                }

                                result = await this.put('/me/player/repeat?state=' + state);
                                if (result) {
                                    callback('repeat is now **'+state+'**');
                                }
                            }

                            break;
                        case 'shuffle':
                            if (playback) {
                                var state = null;

                                if (args.switchOn) {
                                    state = 'true';
                                } else if (args.switchOff) {
                                    state = 'false';
                                } else {
                                    var currentShuffleState = playback.shuffle_state;
                                    state = !currentShuffleState ? 'true' : 'false';
                                }

                                result = await this.put('/me/player/shuffle?state=' + state);
                                if (result) {
                                    callback(state === 'true' ? 'on' : 'off');
                                }
                            }

                            break;
                        case 'volume':
                            var percent = 0;
                            if (args.number) {
                                percent = Math.min(parseFloat(args.number.trim()), 100);
                            }

                            result = await this.put('/me/player/volume?volume_percent=' + percent);
                            if (result) {
                                callback('volume set to **' + percent + '%**');
                            }

                            break;
                    }
                }
            }

            return result;
        } catch (err) {
            console.log(err);
        }
    }
}

export default Spotify;
<|endoftext|>


<commit_before>'use strict'

const winston       = require('winston'),
			fs            = require('fs'),
			path          = require('path'),
			conf          = require('./../config')

const loggerFactory = (_conf, _logdir) => {

	winston.emitErrs = true

	if(_conf.isProd(_conf)){

		const logPath  = path.join(path.dirname(require.main.filename), _logdir)

		fs.existsSync(logPath) || fs.mkdirSync(logPath)

		return new (winston.Logger)({
			transports: [
			new (winston.transports.Console)({
				colorize: true
			}),
			new (winston.transports.File)({
				name: 'info-file',
				filename: path.join(logPath, 'info.log'),
				level: 'info'
			}),
			new (winston.transports.File)({
				name: 'error-file',
				filename: path.join(logPath, 'error.log'),
				level: 'error'
			})
			]
		})
	} else if(_conf.isTest(_conf)) {
		return new (winston.Logger)({
			transports: [
			new (winston.transports.Console)({
				colorize: true,
				level: 'error'
			})
			]
		})
	} else {
		return new (winston.Logger)({
			transports: [
			new (winston.transports.Console)()
			]
		})
	}
}

const logger = loggerFactory(conf, conf.get('LOG_DIR'))

module.exports = logger
module.exports.stream = {
	write: (message, encoding) => {
		logger.info(message)
	}
}<commit_msg>Remoção transporte console para ambiente de testes<commit_after>'use strict'

const winston       = require('winston'),
			fs            = require('fs'),
			path          = require('path'),
			conf          = require('./../config')

const loggerFactory = (_conf, _logdir) => {

	winston.emitErrs = true

	if(_conf.isProd(_conf)){

		const logPath  = path.join(path.dirname(require.main.filename), _logdir)

		fs.existsSync(logPath) || fs.mkdirSync(logPath)

		return new (winston.Logger)({
			transports: [
			new (winston.transports.Console)({
				colorize: true
			}),
			new (winston.transports.File)({
				name: 'info-file',
				filename: path.join(logPath, 'info.log'),
				level: 'info'
			}),
			new (winston.transports.File)({
				name: 'error-file',
				filename: path.join(logPath, 'error.log'),
				level: 'error'
			})
			]
		})
	} else if(_conf.isTest(_conf)) {
		return new (winston.Logger)({
			transports: []
		})
	} else {
		return new (winston.Logger)({
			transports: [
			new (winston.transports.Console)()
			]
		})
	}
}

const logger = loggerFactory(conf, conf.get('LOG_DIR'))

module.exports = logger
module.exports.stream = {
	write: (message, encoding) => {
		logger.info(message)
	}
}<|endoftext|>