This commit is contained in:
MatteZ02 committed 2019-05-30 12:06:47 +03:00
1 parent cbdffcf19c
commit 5eb0264906
2502 files changed
+360854

No files matched your search

+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Hyper-Coder
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+14
View File
@@ -0,0 +1,14 @@
# Simple YouTube API
[![Module support server](https://discordapp.com/api/guilds/430216837276368897/embed.png)](https://discord.gg/A97Qftr)
[![Build Status](https://travis-ci.org/HyperCoder2975/simple-youtube-api.svg?branch=master)](https://travis-ci.org/HyperCoder2975/simple-youtube-api)
This library is designed to greatly simplify interacting with the basic functions of the YouTube API.
It deals with viewing/searching videos, playlists, and channels.
## Installation
$ npm install simple-youtube-api
## Usage
- [Documentation](https://HyperCoder2975.github.io/simple-youtube-api/master/)
- [Examples](https://github.com/HyperCoder2975/simple-youtube-api/tree/master/examples)
+294
View File
@@ -0,0 +1,294 @@
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["SimpleYoutubeAPI"] = factory();
else
root["SimpleYoutubeAPI"] = factory();
})(window, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./src/index.js");
/******/ })
/************************************************************************/
/******/ ({
/***/ "./node_modules/iso8601-duration/lib/index.js":
/*!****************************************************!*\
!*** ./node_modules/iso8601-duration/lib/index.js ***!
\****************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n\tvalue: true\n});\n/**\n * @description A module for parsing ISO8601 durations\n */\n\n/**\n * The pattern used for parsing ISO8601 duration (PnYnMnDTnHnMnS).\n * This does not cover the week format PnW.\n */\n\n// PnYnMnDTnHnMnS\nvar numbers = '\\\\d+(?:[\\\\.,]\\\\d{0,3})?';\nvar weekPattern = '(' + numbers + 'W)';\nvar datePattern = '(' + numbers + 'Y)?(' + numbers + 'M)?(' + numbers + 'D)?';\nvar timePattern = 'T(' + numbers + 'H)?(' + numbers + 'M)?(' + numbers + 'S)?';\n\nvar iso8601 = 'P(?:' + weekPattern + '|' + datePattern + '(?:' + timePattern + ')?)';\nvar objMap = ['weeks', 'years', 'months', 'days', 'hours', 'minutes', 'seconds'];\n\n/**\n * The ISO8601 regex for matching / testing durations\n */\nvar pattern = exports.pattern = new RegExp(iso8601);\n\n/** Parse PnYnMnDTnHnMnS format to object\n * @param {string} durationString - PnYnMnDTnHnMnS formatted string\n * @return {Object} - With a property for each part of the pattern\n */\nvar parse = exports.parse = function parse(durationString) {\n\t// slice away first entry in match-array\n\treturn durationString.match(pattern).slice(1).reduce(function (prev, next, idx) {\n\t\tprev[objMap[idx]] = parseFloat(next) || 0;\n\t\treturn prev;\n\t}, {});\n};\n\n/**\n * Convert ISO8601 duration object to an end Date.\n *\n * @param {Object} duration - The duration object\n * @param {Date} startDate - The starting Date for calculating the duration\n * @return {Date} - The resulting end Date\n */\nvar end = exports.end = function end(duration, startDate) {\n\t// create two equal timestamps, add duration to 'then' and return time difference\n\tvar timestamp = startDate ? startDate.getTime() : Date.now();\n\tvar then = new Date(timestamp);\n\n\tthen.setFullYear(then.getFullYear() + duration.years);\n\tthen.setMonth(then.getMonth() + duration.months);\n\tthen.setDate(then.getDate() + duration.days);\n\tthen.setHours(then.getHours() + duration.hours);\n\tthen.setMinutes(then.getMinutes() + duration.minutes);\n\t// then.setSeconds(then.getSeconds() + duration.seconds);\n\tthen.setMilliseconds(then.getMilliseconds() + duration.seconds * 1000);\n\t// special case weeks\n\tthen.setDate(then.getDate() + duration.weeks * 7);\n\n\treturn then;\n};\n\n/**\n * Convert ISO8601 duration object to seconds\n *\n * @param {Object} duration - The duration object\n * @param {Date} startDate - The starting point for calculating the duration\n * @return {Number}\n */\nvar toSeconds = exports.toSeconds = function toSeconds(duration, startDate) {\n\tvar timestamp = startDate ? startDate.getTime() : Date.now();\n\tvar now = new Date(timestamp);\n\tvar then = end(duration, startDate);\n\n\tvar seconds = (then.getTime() - now.getTime()) / 1000;\n\treturn seconds;\n};\n\nexports.default = {\n\tend: end,\n\ttoSeconds: toSeconds,\n\tpattern: pattern,\n\tparse: parse\n};\n\n//# sourceURL=webpack://SimpleYoutubeAPI/./node_modules/iso8601-duration/lib/index.js?");
/***/ }),
/***/ "./node_modules/node-fetch/browser.js":
/*!********************************************!*\
!*** ./node_modules/node-fetch/browser.js ***!
\********************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
eval("\n\nmodule.exports = exports = self.fetch;\n\n// Needed for TypeScript and Webpack.\nexports.default = self.fetch.bind(self);\n\nexports.Headers = self.Headers;\nexports.Request = self.Request;\nexports.Response = self.Response;\n\n\n//# sourceURL=webpack://SimpleYoutubeAPI/./node_modules/node-fetch/browser.js?");
/***/ }),
/***/ "./node_modules/node-libs-browser/node_modules/punycode/punycode.js":
/*!**************************************************************************!*\
!*** ./node_modules/node-libs-browser/node_modules/punycode/punycode.js ***!
\**************************************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("/* WEBPACK VAR INJECTION */(function(module, global) {var __WEBPACK_AMD_DEFINE_RESULT__;/*! https://mths.be/punycode v1.4.1 by @mathias */\n;(function(root) {\n\n\t/** Detect free variables */\n\tvar freeExports = true && exports &&\n\t\t!exports.nodeType && exports;\n\tvar freeModule = true && module &&\n\t\t!module.nodeType && module;\n\tvar freeGlobal = typeof global == 'object' && global;\n\tif (\n\t\tfreeGlobal.global === freeGlobal ||\n\t\tfreeGlobal.window === freeGlobal ||\n\t\tfreeGlobal.self === freeGlobal\n\t) {\n\t\troot = freeGlobal;\n\t}\n\n\t/**\n\t * The `punycode` object.\n\t * @name punycode\n\t * @type Object\n\t */\n\tvar punycode,\n\n\t/** Highest positive signed 32-bit float value */\n\tmaxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1\n\n\t/** Bootstring parameters */\n\tbase = 36,\n\ttMin = 1,\n\ttMax = 26,\n\tskew = 38,\n\tdamp = 700,\n\tinitialBias = 72,\n\tinitialN = 128, // 0x80\n\tdelimiter = '-', // '\\x2D'\n\n\t/** Regular expressions */\n\tregexPunycode = /^xn--/,\n\tregexNonASCII = /[^\\x20-\\x7E]/, // unprintable ASCII chars + non-ASCII chars\n\tregexSeparators = /[\\x2E\\u3002\\uFF0E\\uFF61]/g, // RFC 3490 separators\n\n\t/** Error messages */\n\terrors = {\n\t\t'overflow': 'Overflow: input needs wider integers to process',\n\t\t'not-basic': 'Illegal input >= 0x80 (not a basic code point)',\n\t\t'invalid-input': 'Invalid input'\n\t},\n\n\t/** Convenience shortcuts */\n\tbaseMinusTMin = base - tMin,\n\tfloor = Math.floor,\n\tstringFromCharCode = String.fromCharCode,\n\n\t/** Temporary variable */\n\tkey;\n\n\t/*--------------------------------------------------------------------------*/\n\n\t/**\n\t * A generic error utility function.\n\t * @private\n\t * @param {String} type The error type.\n\t * @returns {Error} Throws a `RangeError` with the applicable error message.\n\t */\n\tfunction error(type) {\n\t\tthrow new RangeError(errors[type]);\n\t}\n\n\t/**\n\t * A generic `Array#map` utility function.\n\t * @private\n\t * @param {Array} array The array to iterate over.\n\t * @param {Function} callback The function that gets called for every array\n\t * item.\n\t * @returns {Array} A new array of values returned by the callback function.\n\t */\n\tfunction map(array, fn) {\n\t\tvar length = array.length;\n\t\tvar result = [];\n\t\twhile (length--) {\n\t\t\tresult[length] = fn(array[length]);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/**\n\t * A simple `Array#map`-like wrapper to work with domain name strings or email\n\t * addresses.\n\t * @private\n\t * @param {String} domain The domain name or email address.\n\t * @param {Function} callback The function that gets called for every\n\t * character.\n\t * @returns {Array} A new string of characters returned by the callback\n\t * function.\n\t */\n\tfunction mapDomain(string, fn) {\n\t\tvar parts = string.split('@');\n\t\tvar result = '';\n\t\tif (parts.length > 1) {\n\t\t\t// In email addresses, only the domain name should be punycoded. Leave\n\t\t\t// the local part (i.e. everything up to `@`) intact.\n\t\t\tresult = parts[0] + '@';\n\t\t\tstring = parts[1];\n\t\t}\n\t\t// Avoid `split(regex)` for IE8 compatibility. See #17.\n\t\tstring = string.replace(regexSeparators, '\\x2E');\n\t\tvar labels = string.split('.');\n\t\tvar encoded = map(labels, fn).join('.');\n\t\treturn result + encoded;\n\t}\n\n\t/**\n\t * Creates an array containing the numeric code points of each Unicode\n\t * character in the string. While JavaScript uses UCS-2 internally,\n\t * this function will convert a pair of surrogate halves (each of which\n\t * UCS-2 exposes as separate characters) into a single code point,\n\t * matching UTF-16.\n\t * @see `punycode.ucs2.encode`\n\t * @see <https://mathiasbynens.be/notes/javascript-encoding>\n\t * @memberOf punycode.ucs2\n\t * @name decode\n\t * @param {String} string The Unicode input string (UCS-2).\n\t * @returns {Array} The new array of code points.\n\t */\n\tfunction ucs2decode(string) {\n\t\tvar output = [],\n\t\t counter = 0,\n\t\t length = string.length,\n\t\t value,\n\t\t extra;\n\t\twhile (counter < length) {\n\t\t\tvalue = string.charCodeAt(counter++);\n\t\t\tif (value >= 0xD800 && value <= 0xDBFF && counter < length) {\n\t\t\t\t// high surrogate, and there is a next character\n\t\t\t\textra = string.charCodeAt(counter++);\n\t\t\t\tif ((extra & 0xFC00) == 0xDC00) { // low surrogate\n\t\t\t\t\toutput.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);\n\t\t\t\t} else {\n\t\t\t\t\t// unmatched surrogate; only append this code unit, in case the next\n\t\t\t\t\t// code unit is the high surrogate of a surrogate pair\n\t\t\t\t\toutput.push(value);\n\t\t\t\t\tcounter--;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\toutput.push(value);\n\t\t\t}\n\t\t}\n\t\treturn output;\n\t}\n\n\t/**\n\t * Creates a string based on an array of numeric code points.\n\t * @see `punycode.ucs2.decode`\n\t * @memberOf punycode.ucs2\n\t * @name encode\n\t * @param {Array} codePoints The array of numeric code points Line truncated
/***/ }),
/***/ "./node_modules/querystring-es3/decode.js":
/*!************************************************!*\
!*** ./node_modules/querystring-es3/decode.js ***!
\************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
eval("// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n\n// If obj.hasOwnProperty has been overridden, then calling\n// obj.hasOwnProperty(prop) will break.\n// See: https://github.com/joyent/node/issues/1707\nfunction hasOwnProperty(obj, prop) {\n return Object.prototype.hasOwnProperty.call(obj, prop);\n}\n\nmodule.exports = function(qs, sep, eq, options) {\n sep = sep || '&';\n eq = eq || '=';\n var obj = {};\n\n if (typeof qs !== 'string' || qs.length === 0) {\n return obj;\n }\n\n var regexp = /\\+/g;\n qs = qs.split(sep);\n\n var maxKeys = 1000;\n if (options && typeof options.maxKeys === 'number') {\n maxKeys = options.maxKeys;\n }\n\n var len = qs.length;\n // maxKeys <= 0 means that we should not limit keys count\n if (maxKeys > 0 && len > maxKeys) {\n len = maxKeys;\n }\n\n for (var i = 0; i < len; ++i) {\n var x = qs[i].replace(regexp, '%20'),\n idx = x.indexOf(eq),\n kstr, vstr, k, v;\n\n if (idx >= 0) {\n kstr = x.substr(0, idx);\n vstr = x.substr(idx + 1);\n } else {\n kstr = x;\n vstr = '';\n }\n\n k = decodeURIComponent(kstr);\n v = decodeURIComponent(vstr);\n\n if (!hasOwnProperty(obj, k)) {\n obj[k] = v;\n } else if (isArray(obj[k])) {\n obj[k].push(v);\n } else {\n obj[k] = [obj[k], v];\n }\n }\n\n return obj;\n};\n\nvar isArray = Array.isArray || function (xs) {\n return Object.prototype.toString.call(xs) === '[object Array]';\n};\n\n\n//# sourceURL=webpack://SimpleYoutubeAPI/./node_modules/querystring-es3/decode.js?");
/***/ }),
/***/ "./node_modules/querystring-es3/encode.js":
/*!************************************************!*\
!*** ./node_modules/querystring-es3/encode.js ***!
\************************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
eval("// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n\nvar stringifyPrimitive = function(v) {\n switch (typeof v) {\n case 'string':\n return v;\n\n case 'boolean':\n return v ? 'true' : 'false';\n\n case 'number':\n return isFinite(v) ? v : '';\n\n default:\n return '';\n }\n};\n\nmodule.exports = function(obj, sep, eq, name) {\n sep = sep || '&';\n eq = eq || '=';\n if (obj === null) {\n obj = undefined;\n }\n\n if (typeof obj === 'object') {\n return map(objectKeys(obj), function(k) {\n var ks = encodeURIComponent(stringifyPrimitive(k)) + eq;\n if (isArray(obj[k])) {\n return map(obj[k], function(v) {\n return ks + encodeURIComponent(stringifyPrimitive(v));\n }).join(sep);\n } else {\n return ks + encodeURIComponent(stringifyPrimitive(obj[k]));\n }\n }).join(sep);\n\n }\n\n if (!name) return '';\n return encodeURIComponent(stringifyPrimitive(name)) + eq +\n encodeURIComponent(stringifyPrimitive(obj));\n};\n\nvar isArray = Array.isArray || function (xs) {\n return Object.prototype.toString.call(xs) === '[object Array]';\n};\n\nfunction map (xs, f) {\n if (xs.map) return xs.map(f);\n var res = [];\n for (var i = 0; i < xs.length; i++) {\n res.push(f(xs[i], i));\n }\n return res;\n}\n\nvar objectKeys = Object.keys || function (obj) {\n var res = [];\n for (var key in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key);\n }\n return res;\n};\n\n\n//# sourceURL=webpack://SimpleYoutubeAPI/./node_modules/querystring-es3/encode.js?");
/***/ }),
/***/ "./node_modules/querystring-es3/index.js":
/*!***********************************************!*\
!*** ./node_modules/querystring-es3/index.js ***!
\***********************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
eval("\n\nexports.decode = exports.parse = __webpack_require__(/*! ./decode */ \"./node_modules/querystring-es3/decode.js\");\nexports.encode = exports.stringify = __webpack_require__(/*! ./encode */ \"./node_modules/querystring-es3/encode.js\");\n\n\n//# sourceURL=webpack://SimpleYoutubeAPI/./node_modules/querystring-es3/index.js?");
/***/ }),
/***/ "./node_modules/url/url.js":
/*!*********************************!*\
!*** ./node_modules/url/url.js ***!
\*********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
eval("// Copyright Joyent, Inc. and other Node contributors.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a\n// copy of this software and associated documentation files (the\n// \"Software\"), to deal in the Software without restriction, including\n// without limitation the rights to use, copy, modify, merge, publish,\n// distribute, sublicense, and/or sell copies of the Software, and to permit\n// persons to whom the Software is furnished to do so, subject to the\n// following conditions:\n//\n// The above copyright notice and this permission notice shall be included\n// in all copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\n\nvar punycode = __webpack_require__(/*! punycode */ \"./node_modules/node-libs-browser/node_modules/punycode/punycode.js\");\nvar util = __webpack_require__(/*! ./util */ \"./node_modules/url/util.js\");\n\nexports.parse = urlParse;\nexports.resolve = urlResolve;\nexports.resolveObject = urlResolveObject;\nexports.format = urlFormat;\n\nexports.Url = Url;\n\nfunction Url() {\n this.protocol = null;\n this.slashes = null;\n this.auth = null;\n this.host = null;\n this.port = null;\n this.hostname = null;\n this.hash = null;\n this.search = null;\n this.query = null;\n this.pathname = null;\n this.path = null;\n this.href = null;\n}\n\n// Reference: RFC 3986, RFC 1808, RFC 2396\n\n// define these here so at least they only have to be\n// compiled once on the first module load.\nvar protocolPattern = /^([a-z0-9.+-]+:)/i,\n portPattern = /:[0-9]*$/,\n\n // Special case for a simple path URL\n simplePathPattern = /^(\\/\\/?(?!\\/)[^\\?\\s]*)(\\?[^\\s]*)?$/,\n\n // RFC 2396: characters reserved for delimiting URLs.\n // We actually just auto-escape these.\n delims = ['<', '>', '\"', '`', ' ', '\\r', '\\n', '\\t'],\n\n // RFC 2396: characters not allowed for various reasons.\n unwise = ['{', '}', '|', '\\\\', '^', '`'].concat(delims),\n\n // Allowed by RFCs, but cause of XSS attacks. Always escape these.\n autoEscape = ['\\''].concat(unwise),\n // Characters that are never ever allowed in a hostname.\n // Note that any invalid chars are also handled, but these\n // are the ones that are *expected* to be seen, so we fast-path\n // them.\n nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape),\n hostEndingChars = ['/', '?', '#'],\n hostnameMaxLen = 255,\n hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/,\n hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/,\n // protocols that can allow \"unsafe\" and \"unwise\" chars.\n unsafeProtocol = {\n 'javascript': true,\n 'javascript:': true\n },\n // protocols that never have a hostname.\n hostlessProtocol = {\n 'javascript': true,\n 'javascript:': true\n },\n // protocols that always contain a // bit.\n slashedProtocol = {\n 'http': true,\n 'https': true,\n 'ftp': true,\n 'gopher': true,\n 'file': true,\n 'http:': true,\n 'https:': true,\n 'ftp:': true,\n 'gopher:': true,\n 'file:': true\n },\n querystring = __webpack_require__(/*! querystring */ \"./node_modules/querystring-es3/index.js\");\n\nfunction urlParse(url, parseQueryString, slashesDenoteHost) {\n if (url && util.isObject(url) && url instanceof Url) return url;\n\n var u = new Url;\n u.parse(url, parseQueryString, slashesDenoteHost);\n return u;\n}\n\nUrl.prototype.parse = function(url, parseQueryString, slashesDenoteHost) {\n if (!util.isString(url)) {\n throw new TypeError(\"Parameter 'url' must be a string, not \" + typeof url);\n }\n\n // Copy chrome, IE, opera backslash-handling behavior.\n // Back slashes before the query string get converted to forward slashes\n // See: https://code.google.com/p/chromium/issues/detail?id=25916\n var queryIndex = url.indexOf('?'),\n splitter =\n (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#',\n uSplit = url.split(splitter),\n slashRegex = /\\\\/g;\n uSplit[0] = uSplit[0].replace(slashRegex, '/');\n url = uSplit.join(splitter);\n\n var rest = url;\n\n // trim before proceeding.\n // This is to support parse stuff like \" http://foo.com \\n\"\n rest = rest.trim();\n\n if (!slashesDenoteHost && url.split('#').length === 1) {\n // Try fast path regexp\n var simplePath = simplePathPattern.exec(rest);\n if (simplePath) {\n this.path = rest;\n this.href = rest;\n this.pathname Line truncated
/***/ }),
/***/ "./node_modules/url/util.js":
/*!**********************************!*\
!*** ./node_modules/url/util.js ***!
\**********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
eval("\n\nmodule.exports = {\n isString: function(arg) {\n return typeof(arg) === 'string';\n },\n isObject: function(arg) {\n return typeof(arg) === 'object' && arg !== null;\n },\n isNull: function(arg) {\n return arg === null;\n },\n isNullOrUndefined: function(arg) {\n return arg == null;\n }\n};\n\n\n//# sourceURL=webpack://SimpleYoutubeAPI/./node_modules/url/util.js?");
/***/ }),
/***/ "./node_modules/webpack/buildin/global.js":
/*!***********************************!*\
!*** (webpack)/buildin/global.js ***!
\***********************************/
/*! no static exports found */
/***/ (function(module, exports) {
eval("var g;\n\n// This works in non-strict mode\ng = (function() {\n\treturn this;\n})();\n\ntry {\n\t// This works if eval is allowed (see CSP)\n\tg = g || Function(\"return this\")() || (1, eval)(\"this\");\n} catch (e) {\n\t// This works if the window reference is available\n\tif (typeof window === \"object\") g = window;\n}\n\n// g can still be undefined, but nothing to do about it...\n// We return undefined, instead of nothing here, so it's\n// easier to handle this case. if(!global) { ...}\n\nmodule.exports = g;\n\n\n//# sourceURL=webpack://SimpleYoutubeAPI/(webpack)/buildin/global.js?");
/***/ }),
/***/ "./node_modules/webpack/buildin/module.js":
/*!***********************************!*\
!*** (webpack)/buildin/module.js ***!
\***********************************/
/*! no static exports found */
/***/ (function(module, exports) {
eval("module.exports = function(module) {\n\tif (!module.webpackPolyfill) {\n\t\tmodule.deprecate = function() {};\n\t\tmodule.paths = [];\n\t\t// module.parent = undefined by default\n\t\tif (!module.children) module.children = [];\n\t\tObject.defineProperty(module, \"loaded\", {\n\t\t\tenumerable: true,\n\t\t\tget: function() {\n\t\t\t\treturn module.l;\n\t\t\t}\n\t\t});\n\t\tObject.defineProperty(module, \"id\", {\n\t\t\tenumerable: true,\n\t\t\tget: function() {\n\t\t\t\treturn module.i;\n\t\t\t}\n\t\t});\n\t\tmodule.webpackPolyfill = 1;\n\t}\n\treturn module;\n};\n\n\n//# sourceURL=webpack://SimpleYoutubeAPI/(webpack)/buildin/module.js?");
/***/ }),
/***/ "./src/Request.js":
/*!************************!*\
!*** ./src/Request.js ***!
\************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("const fetch = __webpack_require__(/*! node-fetch */ \"./node_modules/node-fetch/browser.js\");\nconst Constants = __webpack_require__(/*! ./util/Constants */ \"./src/util/Constants.js\");\n\nclass Request {\n constructor(youtube) {\n this.youtube = youtube;\n }\n\n /**\n * Make a request to the YouTube API\n * @param {string} endpoint The endpoint to query\n * @param {object} [qs={}] Query strings\n * @returns {Promise<object>}\n */\n make(endpoint, qs = {}) {\n qs = Object.assign({ key: this.youtube.key }, qs);\n const params = Object.keys(qs).filter(k => qs[k]).map(k => `${k}=${qs[k]}`);\n return fetch(encodeURI(`https://www.googleapis.com/youtube/v3/${endpoint}${params.length ? `?${params.join('&')}` : ''}`))\n .then(result => result.json())\n .then(result => {\n if (result.error) return Promise.reject(result.error);\n return result;\n });\n }\n\n /**\n * Get a resource from the YouTube API\n * @param {string} type The type of resource to get\n * @param {object} [qs={}] Any other query options\n * @returns {Promise<object>}\n */\n getResource(type, qs = {}) {\n qs = Object.assign({ part: Constants.PARTS[type] }, qs);\n return this.make(Constants.ENDPOINTS[type], qs).then(result =>\n result.items.length ? result.items[0] : Promise.reject(new Error(`resource ${result.kind} not found`))\n );\n }\n\n /**\n * Get a resource from the YouTube API, by ID\n * @param {string} type The type of resource to get\n * @param {string} id The ID of the resource to get\n * @param {object} [qs={}] Any other query options\n * @returns {Promise<object>}\n */\n getResourceByID(type, id, qs = {}) {\n return this.getResource(type, Object.assign(qs, { id }));\n }\n\n /**\n * Get a video from the YouTube API\n * @param {string} id The video to get\n * @param {object} [options] Any request options\n * @returns {Promise<object>}\n */\n getVideo(id, options) {\n return this.getResourceByID('Videos', id, options);\n }\n\n /**\n * Get a playlist from the YouTube API\n * @param {string} id The playlist to get\n * @param {object} [options] Any request options\n * @returns {Promise<object>}\n */\n getPlaylist(id, options) {\n return this.getResourceByID('Playlists', id, options);\n }\n\n /**\n * Get a channel from the YouTube API\n * @param {string} id The channel to get\n * @param {object} [options] Any request options\n * @returns {Promise<object>}\n */\n getChannel(id, options) {\n return this.getResourceByID('Channels', id, options);\n }\n\n /**\n * Fetch a paginated resource.\n * @param {string} endpoint The endpoint to query.\n * @param {number} [count=Infinity] How many results to retrieve.\n * @param {Object} [options={}] Additional options to send.\n * @param {Array} [fetched=[]] Previously fetched resources.\n * @param {?string} [pageToken] The page token to retrieve.\n * @returns {Promise<Array<object>>}\n */\n getPaginated(endpoint, count = Infinity, options = {}, fetched = [], pageToken = null) {\n if(count < 1) return Promise.reject('Cannot fetch less than 1.');\n\n const limit = count > 50 ? 50 : count;\n return this.make(endpoint, Object.assign(options, { pageToken, maxResults: limit })).then(result => {\n const results = fetched.concat(result.items);\n if(result.nextPageToken && limit !== count) return this.getPaginated(endpoint, count - limit, options, results, result.nextPageToken);\n return results;\n });\n }\n}\n\nmodule.exports = Request;\n\n\n//# sourceURL=webpack://SimpleYoutubeAPI/./src/Request.js?");
/***/ }),
/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("const Request = __webpack_require__(/*! ./Request */ \"./src/Request.js\");\n\nconst Video = __webpack_require__(/*! ./structures/Video */ \"./src/structures/Video.js\");\nconst Playlist = __webpack_require__(/*! ./structures/Playlist */ \"./src/structures/Playlist.js\");\nconst Channel = __webpack_require__(/*! ./structures/Channel */ \"./src/structures/Channel.js\");\n\nconst util = __webpack_require__(/*! ./util */ \"./src/util/index.js\");\nconst Constants = __webpack_require__(/*! ./util/Constants */ \"./src/util/Constants.js\");\n\n/**\n * Information about a thumbnail\n * @typedef {Object} Thumbnail\n * @property {string} url The URL of this thumbnail\n * @property {number} width The width of this thumbnail\n * @property {number} height The height of this thumbnail\n */\n\n/**\n * The YouTube API module\n */\nclass YouTube {\n /**\n * @param {string} key The YouTube Data API v3 key to use\n */\n constructor(key) {\n if (typeof key !== 'string') throw new Error('The YouTube API key you provided was not a string.');\n /**\n * The YouTube Data API v3 key\n * @type {?string}\n */\n this.key = key;\n Object.defineProperty(this, 'key', { enumerable: false });\n\n this.request = new Request(this);\n }\n\n /**\n * Make a request to the YouTube API\n * @param {string} endpoint The endpoint of the API\n * @param {Object} qs The query string options\n * @returns {Promise<Object>}\n */\n\n /**\n * Get a video by URL or ID\n * @param {string} url The video URL or ID\n * @param {Object} [options = {}] Options to request with the video.\n * @returns {Promise<?Video>}\n * @example\n * API.getVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ')\n * .then(results => {\n * console.log(`The video's title is ${results[0].title}`);\n * })\n * .catch(console.error);\n */\n getVideo(url, options = {}) {\n const id = Video.extractID(url);\n if (!id) return Promise.reject(new Error(`No video ID found in URL: ${url}`));\n return this.getVideoByID(id, options);\n }\n\n /**\n * Get a video by ID\n * @param {string} id The video ID\n * @param {Object} [options = {}] Options to request with the video.\n * @returns {Promise<?Video>}\n * @example\n * API.getVideoByID('3odIdmuFfEY')\n * .then(results => {\n * console.log(`The video's title is ${results[0].title}`);\n * })\n * .catch(console.error);\n */\n getVideoByID(id, options = {}) {\n return this.request.getVideo(id, options).then(result => result ? new Video(this, result) : null);\n }\n\n /**\n * Get a playlist by URL or ID\n * @param {string} url The playlist URL or ID\n * @param {Object} [options = {}] Options to request with the playlist.\n * @returns {Promise<?Playlist>}\n * @example\n * API.getPlaylist('https://www.youtube.com/playlist?list=PLuY9odN8x9puRuCxiddyRzJ3F5jR-Gun9')\n * .then(results => {\n * console.log(`The playlist's title is ${results[0].title}`);\n * })\n * .catch(console.error);\n */\n getPlaylist(url, options = {}) {\n const id = Playlist.extractID(url);\n if (!id) return Promise.reject(new Error(`No playlist ID found in URL: ${url}`));\n return this.getPlaylistByID(id, options);\n }\n\n /**\n * Get a playlist by ID\n * @param {string} id The playlist ID\n * @param {Object} [options = {}] Options to request with the playlist.\n * @returns {Promise<?Playlist>}\n * @example\n * API.getPlaylistByID('PL2BN1Zd8U_MsyMeK8r9Vdv1lnQGtoJaSa')\n * .then(results => {\n * console.log(`The playlist's title is ${results[0].title}`);\n * })\n * .catch(console.error);\n */\n getPlaylistByID(id, options = {}) {\n return this.request.getPlaylist(id, options).then(result => result ? new Playlist(this, result) : null);\n }\n\n /**\n * Get a channel by URL or ID\n * @param {string} url The channel URL or ID\n * @param {Object} [options = {}] Options to request with the channel.\n * @returns {Promise<?Channel>}\n * @example\n * API.getChannel('https://www.youtube.com/channel/UC477Kvszl9JivqOxN1dFgPQ')\n * .then(results => {\n * console.log(`The channel's title is ${results[0].title}`);\n * })\n * .catch(console.error);\n */\n getChannel(url, options = {}) {\n const id = Channel.extractID(url);\n if (!id) return Promise.reject(new Error(`No channel ID found in URL: ${url}`));\n return this.getChannelByID(id, options);\n }\n\n /**\n * Get a channel by ID\n * @param {string} id The channel ID\n * @param {Object} [options = {}] Options to request with the channel.\n * @returns {Promise<?Channel>}\n * @example\n * API.getChannelByID('UC477Kvszl9JivqOxN1dFgPQ')\n * .then(results => {Line truncated
/***/ }),
/***/ "./src/structures/Channel.js":
/*!***********************************!*\
!*** ./src/structures/Channel.js ***!
\***********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("const { parseURL } = __webpack_require__(/*! ../util */ \"./src/util/index.js\");\nconst Constants = __webpack_require__(/*! ../util/Constants */ \"./src/util/Constants.js\");\n\n/**\n * Represents a YouTube channel\n * @class\n */\nclass Channel {\n /**\n * @param {YouTube} youtube The YouTube instance creating this\n * @param {Object} data The data of the channel\n */\n constructor(youtube, data) {\n /**\n * The YouTube instance that created this\n * @type {YouTube}\n */\n this.youtube = youtube;\n Object.defineProperty(this, 'youtube', { enumerable: false });\n\n /**\n * The type to filter search results\n * @type {string}\n */\n this.type = 'channel';\n\n this._patch(data);\n }\n\n _patch(data) {\n if (!data) return;\n\n /**\n * Raw data from the YouTube API\n * @type {object}\n */\n this.raw = data;\n\n /**\n * Whether this is a full channel object.\n * @type {boolean}\n */\n this.full = data.kind === Constants.KINDS.Channel;\n\n /**\n * The YouTube resource from which this channel was created.\n * @type {string}\n */\n this.kind = data.kind;\n\n /**\n * This channel's ID\n * @type {string}\n * @name Channel#id\n */\n\n /**\n * This channel's title\n * @type {?string}\n * @name Channel#title\n */\n\n switch (data.kind) {\n case Constants.KINDS.Playlist:\n case Constants.KINDS.PlaylistItem:\n case Constants.KINDS.Video:\n if (data.snippet) {\n this.id = data.snippet.channelId;\n this.title = data.snippet.channelTitle;\n break;\n } else {\n throw new Error('Attempted to make a channel out of a resource with no channel data.');\n }\n case Constants.KINDS.SearchResult:\n if (data.id.kind === Constants.KINDS.Channel) {\n this.id = data.id.channelId;\n break;\n } else if (data.snippet) {\n this.id = data.snippet.channelId;\n this.title = data.snippet.channelTitle;\n break;\n } else {\n throw new Error('Attempted to make a channel out of a search result with no channel data.');\n }\n case Constants.KINDS.Channel:\n this.id = data.id;\n if (data.snippet) {\n this.title = data.snippet.title;\n\n /**\n * This channel's description\n * @type {?string}\n * @name Channel#description\n */\n this.description = data.snippet.description;\n\n /**\n * The channel's custom URL if it has one\n * @type {?string}\n */\n this.customURL = data.snippet.customUrl;\n\n /**\n * The channel's creation date\n * @type {?Date}\n * @name Channel#publishedAt\n */\n this.publishedAt = new Date(data.snippet.publishedAt);\n\n /**\n * The channel's thumbnails: available types are 'default', 'medium', and 'high'\n * @type {?Object.<string, Thumbnail>}\n */\n this.thumbnails = data.snippet.thumbnails;\n\n /**\n * The channel's default language\n * @type {?string}\n */\n this.defaultLanguage = data.snippet.defaultLanguage;\n\n /**\n * Information about the channel as specified in the `hl` query parameter\n * @type {?{title: string, description: string}}\n */\n this.localized = data.snippet.localized;\n\n /**\n * The country of the channel\n * @type {?string}\n */\n this.country = data.snippet.country;\n }\n\n if (data.contentDetails) {\n /**\n * Playlists associated with this channel; all values are playlist IDs\n * @type {?Object}\n * @property {?string} likes The channel's liked videos\n * @property {?string} favorites The channel's favorited videos (note: favorited videos are deprecated)\n * @property {?string} uploads The channel's uploaded videos\n Line truncated
/***/ }),
/***/ "./src/structures/Playlist.js":
/*!************************************!*\
!*** ./src/structures/Playlist.js ***!
\************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("const { parseURL } = __webpack_require__(/*! ../util */ \"./src/util/index.js\");\nconst Constants = __webpack_require__(/*! ../util/Constants */ \"./src/util/Constants.js\");\nconst Video = __webpack_require__(/*! ./Video */ \"./src/structures/Video.js\");\nconst Channel = __webpack_require__(/*! ./Channel */ \"./src/structures/Channel.js\");\n\n/** Represents a YouTube playlist */\nclass Playlist {\n /**\n * @param {YouTube} youtube The YouTube instance creating this\n * @param {Object} data The data of the playlist\n */\n constructor(youtube, data) {\n /**\n * The YouTube instance that created this\n * @type {YouTube}\n */\n this.youtube = youtube;\n Object.defineProperty(this, 'youtube', { enumerable: false });\n\n /**\n * The type to filter search results\n * @type {string}\n */\n this.type = 'playlist';\n\n /**\n * Videos in this playlist. Available after calling {@link Playlist#getVideos}.\n * @type {Array<Video>}\n */\n this.videos = [];\n\n this._patch(data);\n }\n\n _patch(data) {\n if (!data) return;\n\n this.raw = data;\n\n /**\n * The channel this playlist is in\n * @type {Channel}\n */\n this.channel = new Channel(this.youtube, data);\n\n /**\n * This playlist's ID\n * @type {string}\n * @name Playlist#id\n */\n\n switch (data.kind) {\n case Constants.KINDS.SearchResult:\n if (data.id.kind === Constants.KINDS.Playlist) this.id = data.id.playlistId;\n else throw new Error('Attempted to make a playlist out of a non-playlist search result.');\n break;\n case Constants.KINDS.Playlist:\n this.id = data.id;\n break;\n case Constants.KINDS.PlaylistItem:\n if (data.snippet) this.id = data.snippet.playlistId;\n else throw new Error('Attempted to make a playlist out of a resource with no playlist data.');\n return this; // don't pull extra info from playlist item info\n default:\n throw new Error(`Unknown playlist kind: ${data.kind}.`);\n }\n\n if (data.snippet) {\n /**\n * This playlist's title\n * @type {?string}\n */\n this.title = data.snippet.title;\n\n /**\n * This playlist's description\n * @type {?string}\n */\n this.description = data.snippet.description;\n\n /**\n * The date/time this playlist was published\n * @type {?Date}\n */\n this.publishedAt = new Date(data.snippet.publishedAt);\n\n /**\n * Thumbnails for this playlist\n * @type {?Object.<string, Thumbnail>}\n */\n this.thumbnails = data.snippet.thumbnails;\n\n /**\n * Channel title of this playlist\n * @type {?string}\n */\n this.channelTitle = data.snippet.channelTitle;\n\n /**\n * The language in this playlist's title and description\n * @type {?string}\n */\n this.defaultLanguage = data.snippet.defaultLanguage;\n\n /**\n * Information about the playlist as specified in the `hl` parameter\n * @type {?{title: string, description: string}}\n */\n this.localized = data.snippet.localized;\n }\n\n if (data.status) {\n /**\n * The privacy status of this video\n * @type {string}\n */\n this.privacy = data.status.privacyStatus;\n }\n\n if (data.contentDetails) {\n /**\n * The total number of videos in this playlist\n * @type {number}\n */\n this.length = data.contentDetails.itemCount;\n }\n\n if (data.player) {\n /**\n * A string with an iframe tag for embedding this playlist\n * @type {string}\n */\n this.embedHTML = data.player.embedHtml;\n }\n\n return this;\n }\n\n /**\n * The URL to this playlist\n * @type {string}\n */\n get url() {\n return `https://www.youtube.com/playlist?list=${this.id}`;\n }\n\n /**\n * Fetch the full representation of this playlist.\n * @param {object} [options] Any extra query params\n * @returns {Playlist}\n */\n fetch(options) {\n return this.youtube.request.getPlaylist(this.id, options).then(this._patch.bind(this));\n }\n\n /**\n * Gets videos in the playlist\n * @param {Number} [limit] Maximum number of videos to obtain. Fetches all if not provided.\n * @param {Obj Line truncated
/***/ }),
/***/ "./src/structures/Video.js":
/*!*********************************!*\
!*** ./src/structures/Video.js ***!
\*********************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("const duration = __webpack_require__(/*! iso8601-duration */ \"./node_modules/iso8601-duration/lib/index.js\");\r\nconst { parseURL } = __webpack_require__(/*! ../util */ \"./src/util/index.js\");\r\nconst Constants = __webpack_require__(/*! ../util/Constants */ \"./src/util/Constants.js\");\r\nconst Channel = __webpack_require__(/*! ./Channel */ \"./src/structures/Channel.js\");\r\n\r\n/** Represents a YouTube video */\r\nclass Video {\r\n /**\r\n * @param {YouTube} youtube The YouTube instance creating this\r\n * @param {Object} data The data of the video\r\n */\r\n constructor(youtube, data) {\r\n /**\r\n * The YouTube instance that created this\r\n * @type {YouTube}\r\n */\r\n this.youtube = youtube;\r\n Object.defineProperty(this, 'youtube', { enumerable: false });\r\n\r\n /**\r\n * The type to filter search results\r\n * @type {string}\r\n */\r\n this.type = 'video';\r\n\r\n this._patch(data);\r\n }\r\n\r\n _patch(data) {\r\n if (!data) return;\r\n\r\n /**\r\n * The raw data from the YouTube API.\r\n * @type {object}\r\n */\r\n this.raw = data;\r\n\r\n /**\r\n * Whether this is a full (returned from the videos API end point) or partial video (returned\r\n * as part of another resource).\r\n * @type {boolean}\r\n */\r\n this.full = data.kind === Constants.KINDS.Video;\r\n\r\n /**\r\n * The resource that this video was created from.\r\n * @type {string}\r\n */\r\n this.kind = data.kind;\r\n\r\n /**\r\n * This video's ID\r\n * @type {string}\r\n * @name Video#id\r\n */\r\n\r\n switch (data.kind) {\r\n case Constants.KINDS.PlaylistItem:\r\n if (data.snippet) {\r\n if (data.snippet.resourceId.kind === Constants.KINDS.Video) this.id = data.snippet.resourceId.videoId;\r\n else throw new Error('Attempted to make a video out of a non-video playlist item.');\r\n break;\r\n } else {\r\n throw new Error('Attempted to make a video out of a playlist item with no video data.');\r\n }\r\n case Constants.KINDS.Video:\r\n this.id = data.id;\r\n break;\r\n case Constants.KINDS.SearchResult:\r\n if (data.id.kind === Constants.KINDS.Video) this.id = data.id.videoId;\r\n else throw new Error('Attempted to make a video out of a non-video search result.');\r\n break;\r\n default:\r\n throw new Error(`Unknown video kind: ${data.kind}.`);\r\n }\r\n\r\n if (data.snippet) {\r\n /**\r\n * This video's title\r\n * @type {string}\r\n */\r\n this.title = data.snippet.title;\r\n\r\n /**\r\n * This video's description\r\n * @type {string}\r\n */\r\n this.description = data.snippet.description;\r\n\r\n /**\r\n * The thumbnails of this video.\r\n * @type {Object.<'default', 'medium', 'high', 'standard', 'maxres'>}\r\n */\r\n this.thumbnails = data.snippet.thumbnails;\r\n\r\n /**\r\n * The date/time this video was published\r\n * @type {Date}\r\n */\r\n this.publishedAt = new Date(data.snippet.publishedAt);\r\n\r\n /**\r\n * The channel this video is in.\r\n * @type {Channel}\r\n */\r\n this.channel = new Channel(this.youtube, data);\r\n }\r\n\r\n if(data.contentDetails) {\r\n /**\r\n * An object containing time period information. All properties are integers, and do not include the lower\r\n * precision ones.\r\n * @typedef {Object} DurationObject\r\n * @property {number} [hours] How many hours the video is long\r\n * @property {number} [minutes] How many minutes the video is long\r\n * @property {number} [seconds] How many seconds the video is long\r\n */\r\n\r\n /**\r\n * The duration of the video\r\n * @type {?DurationObject}\r\n */\r\n this.duration = data.contentDetails.duration ? duration.parse(data.contentDetails.duration) : null;\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * The maxiumum available resolution thumbnail.\r\n * @type {object}\r\n */\r\n get maxRes() {\r\n const t = this.thumbnails;\r\n return t.maxres || t.standard || t.high || t.medium || t.default;\r\n }\r\n\r\n /**\r\n * The URL to this video\r\n * @type {string}\r\n */\r\n get url() {\Line truncated
/***/ }),
/***/ "./src/util/Constants.js":
/*!*******************************!*\
!*** ./src/util/Constants.js ***!
\*******************************/
/*! no static exports found */
/***/ (function(module, exports) {
eval("exports.PARTS = {\n Search: 'snippet',\n Videos: 'snippet,contentDetails',\n Playlists: 'snippet',\n PlaylistItems: 'snippet,status',\n Channels: 'snippet'\n};\n\nexports.KINDS = {\n Video: 'youtube#video',\n PlaylistItem: 'youtube#playlistItem',\n Playlist: 'youtube#playlist',\n SearchResult: 'youtube#searchResult',\n Channel: 'youtube#channel'\n};\n\nexports.ENDPOINTS = {\n PlaylistItems: 'playlistItems',\n Channels: 'channels',\n Videos: 'videos',\n Playlists: 'playlists',\n Search: 'search'\n};\n\n\n//# sourceURL=webpack://SimpleYoutubeAPI/./src/util/Constants.js?");
/***/ }),
/***/ "./src/util/index.js":
/*!***************************!*\
!*** ./src/util/index.js ***!
\***************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("const { parse } = __webpack_require__(/*! url */ \"./node_modules/url/url.js\");\n\n/**\n * Parse a string as a potential YouTube resource URL.\n * @param {string} url\n * @returns {{video: ?string, channel: ?string, playlist: ?string}}\n */\nexports.parseURL = (url) => {\n const parsed = parse(url, true);\n switch (parsed.hostname) {\n case 'www.youtube.com':\n case 'youtube.com':\n case 'm.youtube.com': {\n const idRegex = /^[a-zA-Z0-9-_]+$/;\n if (parsed.pathname === '/watch') {\n if (!idRegex.test(parsed.query.v)) return {};\n const response = { video: parsed.query.v };\n if (parsed.query.list) response.playlist = parsed.query.list;\n return response;\n } else if (parsed.pathname === '/playlist') {\n if(!idRegex.test(parsed.query.list)) return {};\n return { playlist: parsed.query.list };\n } else if (parsed.pathname.startsWith('/channel/')) {\n const id = parsed.pathname.replace('/channel/', '');\n if (!idRegex.test(id)) return {};\n return { channel: id };\n }\n\n return {};\n }\n case 'youtu.be':\n return { video: /^\\/[a-zA-Z0-9-_]+$/.test(parsed.pathname) ? parsed.pathname.slice(1) : null };\n default:\n return {};\n }\n};\n\n\n//# sourceURL=webpack://SimpleYoutubeAPI/./src/util/index.js?");
/***/ })
/******/ });
});
+1
View File
@@ -0,0 +1 @@
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.SimpleYoutubeAPI=e():t.SimpleYoutubeAPI=e()}(window,function(){return function(t){var e={};function s(n){if(e[n])return e[n].exports;var i=e[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,s),i.l=!0,i.exports}return s.m=t,s.c=e,s.d=function(t,e,n){s.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},s.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},s.t=function(t,e){if(1&e&&(t=s(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(s.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)s.d(n,i,function(e){return t[e]}.bind(null,i));return n},s.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return s.d(e,"a",e),e},s.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},s.p="",s(s.s=4)}([function(t,e){e.PARTS={Search:"snippet",Videos:"snippet,contentDetails",Playlists:"snippet",PlaylistItems:"snippet,status",Channels:"snippet"},e.KINDS={Video:"youtube#video",PlaylistItem:"youtube#playlistItem",Playlist:"youtube#playlist",SearchResult:"youtube#searchResult",Channel:"youtube#channel"},e.ENDPOINTS={PlaylistItems:"playlistItems",Channels:"channels",Videos:"videos",Playlists:"playlists",Search:"search"}},function(t,e,s){const{parse:n}=s(8);e.parseURL=(t=>{const e=n(t,!0);switch(e.hostname){case"www.youtube.com":case"youtube.com":case"m.youtube.com":{const t=/^[a-zA-Z0-9-_]+$/;if("/watch"===e.pathname){if(!t.test(e.query.v))return{};const s={video:e.query.v};return e.query.list&&(s.playlist=e.query.list),s}if("/playlist"===e.pathname)return t.test(e.query.list)?{playlist:e.query.list}:{};if(e.pathname.startsWith("/channel/")){const s=e.pathname.replace("/channel/","");return t.test(s)?{channel:s}:{}}return{}}case"youtu.be":return{video:/^\/[a-zA-Z0-9-_]+$/.test(e.pathname)?e.pathname.slice(1):null};default:return{}}})},function(t,e,s){const{parseURL:n}=s(1),i=s(0);t.exports=class{constructor(t,e){this.youtube=t,Object.defineProperty(this,"youtube",{enumerable:!1}),this.type="channel",this._patch(e)}_patch(t){if(t){switch(this.raw=t,this.full=t.kind===i.KINDS.Channel,this.kind=t.kind,t.kind){case i.KINDS.Playlist:case i.KINDS.PlaylistItem:case i.KINDS.Video:if(t.snippet){this.id=t.snippet.channelId,this.title=t.snippet.channelTitle;break}throw new Error("Attempted to make a channel out of a resource with no channel data.");case i.KINDS.SearchResult:if(t.id.kind===i.KINDS.Channel){this.id=t.id.channelId;break}if(t.snippet){this.id=t.snippet.channelId,this.title=t.snippet.channelTitle;break}throw new Error("Attempted to make a channel out of a search result with no channel data.");case i.KINDS.Channel:this.id=t.id,t.snippet&&(this.title=t.snippet.title,this.description=t.snippet.description,this.customURL=t.snippet.customUrl,this.publishedAt=new Date(t.snippet.publishedAt),this.thumbnails=t.snippet.thumbnails,this.defaultLanguage=t.snippet.defaultLanguage,this.localized=t.snippet.localized,this.country=t.snippet.country),t.contentDetails&&(this.relatedPlaylists=t.contentDetails.relatedPlaylists),t.statistics&&(this.viewCount=t.statistics.viewCount,this.commentCount=t.statistics.commentCount,this.subscriberCount=t.statistics.subscriberCount,this.hiddenSubscriberCount=t.statistics.hiddenSubscriberCount,this.videoCount=t.statistics.videoCount);break;default:throw new Error(`Unknown channel kind: ${t.kind}.`)}return this}}fetch(t){return this.youtube.request.getChannel(this.id,t).then(this._patch.bind(this))}get url(){return`https://www.youtube.com/channel/${this.id}`}static extractID(t){return n(t).channel}}},function(t,e,s){const n=s(7),{parseURL:i}=s(1),r=s(0),o=s(2);t.exports=class{constructor(t,e){this.youtube=t,Object.defineProperty(this,"youtube",{enumerable:!1}),this.type="video",this._patch(e)}_patch(t){if(t){switch(this.raw=t,this.full=t.kind===r.KINDS.Video,this.kind=t.kind,t.kind){case r.KINDS.PlaylistItem:if(t.snippet){if(t.snippet.resourceId.kind!==r.KINDS.Video)throw new Error("Attempted to make a video out of a non-video playlist item.");this.id=t.snippet.resourceId.videoId;break}throw new Error("Attempted to make a video out of a playlist item with no video data.");case r.KINDS.Video:this.id=t.id;break;case r.KINDS.SearchResult:if(t.id.kind!==r.KINDS.Video)throw new Error("Attempted to make a video out of a non-video search result.");this.id=t.id.videoId;break;default:throw new Error(`Unknown video kind: ${t.kind}.`)}return t.snippet&&(this.title=t.snippet.title,this.description=t.snippet.description,this.thumbnails=t.snippet.thumbnails,this.publishedAt=new Date(t.snippet.publishedAt),this.channel=new o(this.youtube,t)),t.contentDetails&&(this.durationLine truncated
+13
View File
@@ -0,0 +1,13 @@
const YouTube = require('simple-youtube-api');
const youtube = new YouTube(' Y o u r A p i K e y ');
youtube.getPlaylist('https://www.youtube.com/playlist?list=PL2BN1Zd8U_MsyMeK8r9Vdv1lnQGtoJaSa')
.then(playlist => {
console.log(`The playlist's title is ${playlist.title}`);
playlist.getVideos()
.then(videos => {
console.log(`This playlist has ${videos.length === 50 ? '50+' : videos.length} videos.`);
})
.catch(console.log);
})
.catch(console.log);
+8
View File
@@ -0,0 +1,8 @@
const YouTube = require('simple-youtube-api');
const youtube = new YouTube(' Y o u r A p i K e y ');
youtube.searchVideos('Centuries', 4)
.then(results => {
console.log(`The video's title is ${results[0].title}`);
})
.catch(console.log);
+8
View File
@@ -0,0 +1,8 @@
const YouTube = require('simple-youtube-api');
const youtube = new YouTube(' Y o u r A p i K e y ');
youtube.videoById('https://www.youtube.com/watch?v=3odIdmuFfEY')
.then(video => {
console.log(`The video's title is ${video[0].title}`);
})
.catch(console.log);
+70
View File
@@ -0,0 +1,70 @@
{
"_from": "simple-youtube-api",
"_id": "simple-youtube-api@5.1.1",
"_inBundle": false,
"_integrity": "sha512-cbWRGTDjWDrC+8LR3gpLaXeRb0+e/eiX/bKYIS0tQ4gBUimhXVhsWWhzWJ49aTBnAA0vChrD3U0ZDL8/CUWNcA==",
"_location": "/simple-youtube-api",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "simple-youtube-api",
"name": "simple-youtube-api",
"escapedName": "simple-youtube-api",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/simple-youtube-api/-/simple-youtube-api-5.1.1.tgz",
"_shasum": "6e6d3892f6651c87cd7591d07f8e0259860447b4",
"_spec": "simple-youtube-api",
"_where": "C:\\Users\\matia\\Bot Files",
"author": {
"name": "Hyper-Coder"
},
"bugs": {
"url": "https://github.com/HyperCoder2975/simple-youtube-api/issues"
},
"bundleDependencies": false,
"dependencies": {
"iso8601-duration": "^1.0.0",
"node-fetch": "^2.2.1"
},
"deprecated": false,
"description": "A module to simplify the YouTube API.",
"devDependencies": {
"dotenv": "^4.0.0",
"jsdoc": "^3.4.0",
"lodash.defaultsdeep": "^4.6.0",
"minami": "^1.1.0",
"mocha": "^3.5.3",
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2"
},
"homepage": "https://github.com/HyperCoder2975/simple-youtube-api",
"license": "MIT",
"main": "src/index.js",
"maintainers": [
{
"name": "Will Nelson",
"email": "will@pleb.xyz"
}
],
"name": "simple-youtube-api",
"repository": {
"type": "git",
"url": "git+https://github.com/HyperCoder2975/simple-youtube-api.git"
},
"scripts": {
"build": "webpack",
"docs": "jsdoc -c jsdoc.json",
"test": "mocha"
},
"sideEffects": false,
"unpkg": "dist/sya.min.js",
"version": "5.1.1"
}
+101
View File
@@ -0,0 +1,101 @@
const fetch = require('node-fetch');
const Constants = require('./util/Constants');
class Request {
constructor(youtube) {
this.youtube = youtube;
}
/**
* Make a request to the YouTube API
* @param {string} endpoint The endpoint to query
* @param {object} [qs={}] Query strings
* @returns {Promise<object>}
*/
make(endpoint, qs = {}) {
qs = Object.assign({ key: this.youtube.key }, qs);
const params = Object.keys(qs).filter(k => qs[k]).map(k => `${k}=${qs[k]}`);
return fetch(encodeURI(`https://www.googleapis.com/youtube/v3/${endpoint}${params.length ? `?${params.join('&')}` : ''}`))
.then(result => result.json())
.then(result => {
if (result.error) return Promise.reject(result.error);
return result;
});
}
/**
* Get a resource from the YouTube API
* @param {string} type The type of resource to get
* @param {object} [qs={}] Any other query options
* @returns {Promise<object>}
*/
getResource(type, qs = {}) {
qs = Object.assign({ part: Constants.PARTS[type] }, qs);
return this.make(Constants.ENDPOINTS[type], qs).then(result =>
result.items.length ? result.items[0] : Promise.reject(new Error(`resource ${result.kind} not found`))
);
}
/**
* Get a resource from the YouTube API, by ID
* @param {string} type The type of resource to get
* @param {string} id The ID of the resource to get
* @param {object} [qs={}] Any other query options
* @returns {Promise<object>}
*/
getResourceByID(type, id, qs = {}) {
return this.getResource(type, Object.assign(qs, { id }));
}
/**
* Get a video from the YouTube API
* @param {string} id The video to get
* @param {object} [options] Any request options
* @returns {Promise<object>}
*/
getVideo(id, options) {
return this.getResourceByID('Videos', id, options);
}
/**
* Get a playlist from the YouTube API
* @param {string} id The playlist to get
* @param {object} [options] Any request options
* @returns {Promise<object>}
*/
getPlaylist(id, options) {
return this.getResourceByID('Playlists', id, options);
}
/**
* Get a channel from the YouTube API
* @param {string} id The channel to get
* @param {object} [options] Any request options
* @returns {Promise<object>}
*/
getChannel(id, options) {
return this.getResourceByID('Channels', id, options);
}
/**
* Fetch a paginated resource.
* @param {string} endpoint The endpoint to query.
* @param {number} [count=Infinity] How many results to retrieve.
* @param {Object} [options={}] Additional options to send.
* @param {Array} [fetched=[]] Previously fetched resources.
* @param {?string} [pageToken] The page token to retrieve.
* @returns {Promise<Array<object>>}
*/
getPaginated(endpoint, count = Infinity, options = {}, fetched = [], pageToken = null) {
if(count < 1) return Promise.reject('Cannot fetch less than 1.');
const limit = count > 50 ? 50 : count;
return this.make(endpoint, Object.assign(options, { pageToken, maxResults: limit })).then(result => {
const results = fetched.concat(result.items);
if(result.nextPageToken && limit !== count) return this.getPaginated(endpoint, count - limit, options, results, result.nextPageToken);
return results;
});
}
}
module.exports = Request;
+226
View File
@@ -0,0 +1,226 @@
const Request = require('./Request');
const Video = require('./structures/Video');
const Playlist = require('./structures/Playlist');
const Channel = require('./structures/Channel');
const util = require('./util');
const Constants = require('./util/Constants');
/**
* Information about a thumbnail
* @typedef {Object} Thumbnail
* @property {string} url The URL of this thumbnail
* @property {number} width The width of this thumbnail
* @property {number} height The height of this thumbnail
*/
/**
* The YouTube API module
*/
class YouTube {
/**
* @param {string} key The YouTube Data API v3 key to use
*/
constructor(key) {
if (typeof key !== 'string') throw new Error('The YouTube API key you provided was not a string.');
/**
* The YouTube Data API v3 key
* @type {?string}
*/
this.key = key;
Object.defineProperty(this, 'key', { enumerable: false });
this.request = new Request(this);
}
/**
* Make a request to the YouTube API
* @param {string} endpoint The endpoint of the API
* @param {Object} qs The query string options
* @returns {Promise<Object>}
*/
/**
* Get a video by URL or ID
* @param {string} url The video URL or ID
* @param {Object} [options = {}] Options to request with the video.
* @returns {Promise<?Video>}
* @example
* API.getVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ')
* .then(results => {
* console.log(`The video's title is ${results[0].title}`);
* })
* .catch(console.error);
*/
getVideo(url, options = {}) {
const id = Video.extractID(url);
if (!id) return Promise.reject(new Error(`No video ID found in URL: ${url}`));
return this.getVideoByID(id, options);
}
/**
* Get a video by ID
* @param {string} id The video ID
* @param {Object} [options = {}] Options to request with the video.
* @returns {Promise<?Video>}
* @example
* API.getVideoByID('3odIdmuFfEY')
* .then(results => {
* console.log(`The video's title is ${results[0].title}`);
* })
* .catch(console.error);
*/
getVideoByID(id, options = {}) {
return this.request.getVideo(id, options).then(result => result ? new Video(this, result) : null);
}
/**
* Get a playlist by URL or ID
* @param {string} url The playlist URL or ID
* @param {Object} [options = {}] Options to request with the playlist.
* @returns {Promise<?Playlist>}
* @example
* API.getPlaylist('https://www.youtube.com/playlist?list=PLuY9odN8x9puRuCxiddyRzJ3F5jR-Gun9')
* .then(results => {
* console.log(`The playlist's title is ${results[0].title}`);
* })
* .catch(console.error);
*/
getPlaylist(url, options = {}) {
const id = Playlist.extractID(url);
if (!id) return Promise.reject(new Error(`No playlist ID found in URL: ${url}`));
return this.getPlaylistByID(id, options);
}
/**
* Get a playlist by ID
* @param {string} id The playlist ID
* @param {Object} [options = {}] Options to request with the playlist.
* @returns {Promise<?Playlist>}
* @example
* API.getPlaylistByID('PL2BN1Zd8U_MsyMeK8r9Vdv1lnQGtoJaSa')
* .then(results => {
* console.log(`The playlist's title is ${results[0].title}`);
* })
* .catch(console.error);
*/
getPlaylistByID(id, options = {}) {
return this.request.getPlaylist(id, options).then(result => result ? new Playlist(this, result) : null);
}
/**
* Get a channel by URL or ID
* @param {string} url The channel URL or ID
* @param {Object} [options = {}] Options to request with the channel.
* @returns {Promise<?Channel>}
* @example
* API.getChannel('https://www.youtube.com/channel/UC477Kvszl9JivqOxN1dFgPQ')
* .then(results => {
* console.log(`The channel's title is ${results[0].title}`);
* })
* .catch(console.error);
*/
getChannel(url, options = {}) {
const id = Channel.extractID(url);
if (!id) return Promise.reject(new Error(`No channel ID found in URL: ${url}`));
return this.getChannelByID(id, options);
}
/**
* Get a channel by ID
* @param {string} id The channel ID
* @param {Object} [options = {}] Options to request with the channel.
* @returns {Promise<?Channel>}
* @example
* API.getChannelByID('UC477Kvszl9JivqOxN1dFgPQ')
* .then(results => {
* console.log(`The channel's title is ${results[0].title}`);
* })
* .catch(console.error);
*/
getChannelByID(id, options = {}) {
return this.request.getChannel(id, options).then(result => result ? new Channel(this, result) : null);
}
/**
* Search YouTube for videos, playlists, and channels
* @param {string} query The string to search for
* @param {number} [limit = 5] Maximum results to obtain
* @param {Object} [options] Additional options to pass to the API request
* @returns {Promise<Array<Video|Playlist|Channel|null>>}
* @example
* API.search('Centuries')
* .then(results => {
* console.log(`I got ${results.length} results`);
* })
* .catch(console.error);
*/
search(query, limit = 5, options = {}) {
return this.request.getPaginated(Constants.ENDPOINTS.Search, limit, Object.assign(options, { q: query, part: Constants.PARTS.Search }))
.then(result => result.map(item => {
if (item.id.kind === Constants.KINDS.Video) return new Video(this, item);
if (item.id.kind === Constants.KINDS.Playlist) return new Playlist(this, item);
if (item.id.kind === Constants.KINDS.Channel) return new Channel(this, item);
return null;
}));
}
/**
* Search YouTube for videos
* @param {string} query The string to search for
* @param {number} [limit = 5] Maximum results to obtain
* @param {Object} [options] Additional options to pass to the API request
* @returns {Promise<Video[]>}
* @example
* API.searchVideos('Centuries')
* .then(results => {
* console.log(`I got ${results.length} videos`);
* })
* .catch(console.error);
*/
searchVideos(query, limit = 5, options = {}) {
return this.search(query, limit, Object.assign(options, { type: 'video' }));
}
/**
* Search YouTube for playlists
* @param {string} query The string to search for
* @param {number} [limit = 5] Maximum results to obtain
* @param {Object} [options] Additional options to pass to the API request
* @returns {Promise<Playlist[]>}
* @example
* API.searchPlaylists('Centuries')
* .then(results => {
* console.log(`I got ${results.length} playlists`);
* })
* .catch(console.error);
*/
searchPlaylists(query, limit = 5, options = {}) {
return this.search(query, limit, Object.assign(options, { type: 'playlist' }));
}
/**
* Search YouTube for channels
* @param {string} query The string to search for
* @param {number} [limit = 5] Maximum results to obtain
* @param {Object} [options] Additional options to pass to the API request
* @returns {Promise<Channel[]>}
* @example
* API.searchChannels('Centuries')
* .then(results => {
* console.log(`I got ${results.length} channels`);
* })
* .catch(console.error);
*/
searchChannels(query, limit = 5, options = {}) {
return this.search(query, limit, Object.assign(options, { type: 'channel' }));
}
}
YouTube.Video = Video;
YouTube.Playlist = Playlist;
YouTube.Channel = Channel;
YouTube.util = util;
module.exports = YouTube;
+212
View File
@@ -0,0 +1,212 @@
const { parseURL } = require('../util');
const Constants = require('../util/Constants');
/**
* Represents a YouTube channel
* @class
*/
class Channel {
/**
* @param {YouTube} youtube The YouTube instance creating this
* @param {Object} data The data of the channel
*/
constructor(youtube, data) {
/**
* The YouTube instance that created this
* @type {YouTube}
*/
this.youtube = youtube;
Object.defineProperty(this, 'youtube', { enumerable: false });
/**
* The type to filter search results
* @type {string}
*/
this.type = 'channel';
this._patch(data);
}
_patch(data) {
if (!data) return;
/**
* Raw data from the YouTube API
* @type {object}
*/
this.raw = data;
/**
* Whether this is a full channel object.
* @type {boolean}
*/
this.full = data.kind === Constants.KINDS.Channel;
/**
* The YouTube resource from which this channel was created.
* @type {string}
*/
this.kind = data.kind;
/**
* This channel's ID
* @type {string}
* @name Channel#id
*/
/**
* This channel's title
* @type {?string}
* @name Channel#title
*/
switch (data.kind) {
case Constants.KINDS.Playlist:
case Constants.KINDS.PlaylistItem:
case Constants.KINDS.Video:
if (data.snippet) {
this.id = data.snippet.channelId;
this.title = data.snippet.channelTitle;
break;
} else {
throw new Error('Attempted to make a channel out of a resource with no channel data.');
}
case Constants.KINDS.SearchResult:
if (data.id.kind === Constants.KINDS.Channel) {
this.id = data.id.channelId;
break;
} else if (data.snippet) {
this.id = data.snippet.channelId;
this.title = data.snippet.channelTitle;
break;
} else {
throw new Error('Attempted to make a channel out of a search result with no channel data.');
}
case Constants.KINDS.Channel:
this.id = data.id;
if (data.snippet) {
this.title = data.snippet.title;
/**
* This channel's description
* @type {?string}
* @name Channel#description
*/
this.description = data.snippet.description;
/**
* The channel's custom URL if it has one
* @type {?string}
*/
this.customURL = data.snippet.customUrl;
/**
* The channel's creation date
* @type {?Date}
* @name Channel#publishedAt
*/
this.publishedAt = new Date(data.snippet.publishedAt);
/**
* The channel's thumbnails: available types are 'default', 'medium', and 'high'
* @type {?Object.<string, Thumbnail>}
*/
this.thumbnails = data.snippet.thumbnails;
/**
* The channel's default language
* @type {?string}
*/
this.defaultLanguage = data.snippet.defaultLanguage;
/**
* Information about the channel as specified in the `hl` query parameter
* @type {?{title: string, description: string}}
*/
this.localized = data.snippet.localized;
/**
* The country of the channel
* @type {?string}
*/
this.country = data.snippet.country;
}
if (data.contentDetails) {
/**
* Playlists associated with this channel; all values are playlist IDs
* @type {?Object}
* @property {?string} likes The channel's liked videos
* @property {?string} favorites The channel's favorited videos (note: favorited videos are deprecated)
* @property {?string} uploads The channel's uploaded videos
*/
this.relatedPlaylists = data.contentDetails.relatedPlaylists;
}
if (data.statistics) {
/**
* The number of times the channel has been viewed
* @type {?number}
*/
this.viewCount = data.statistics.viewCount;
/**
* The number of comments on the channel
* @type {?number}
*/
this.commentCount = data.statistics.commentCount;
/**
* The number of subscribers the channel has
* @type {?number}
*/
this.subscriberCount = data.statistics.subscriberCount;
/**
* Whether the channel's subscriber count is public
* @type {?boolean}
*/
this.hiddenSubscriberCount = data.statistics.hiddenSubscriberCount;
/**
* The number of videos this channel has uploaded
* @type {?number}
*/
this.videoCount = data.statistics.videoCount;
}
break;
default:
throw new Error(`Unknown channel kind: ${data.kind}.`);
}
return this;
}
/**
* Fetch the full representation of this channel.
* @param {object} [options] Any extra query params
* @returns {Channel}
*/
fetch(options) {
return this.youtube.request.getChannel(this.id, options).then(this._patch.bind(this));
}
/**
* The URL to this channel
* @type {string}
*/
get url() {
return `https://www.youtube.com/channel/${this.id}`;
}
/**
* Get a channel ID from a string (URL or ID)
* @param {string} url The string to get the ID from
* @returns {?string}
*/
static extractID(url) {
return parseURL(url).channel;
}
}
module.exports = Channel;
+180
View File
@@ -0,0 +1,180 @@
const { parseURL } = require('../util');
const Constants = require('../util/Constants');
const Video = require('./Video');
const Channel = require('./Channel');
/** Represents a YouTube playlist */
class Playlist {
/**
* @param {YouTube} youtube The YouTube instance creating this
* @param {Object} data The data of the playlist
*/
constructor(youtube, data) {
/**
* The YouTube instance that created this
* @type {YouTube}
*/
this.youtube = youtube;
Object.defineProperty(this, 'youtube', { enumerable: false });
/**
* The type to filter search results
* @type {string}
*/
this.type = 'playlist';
/**
* Videos in this playlist. Available after calling {@link Playlist#getVideos}.
* @type {Array<Video>}
*/
this.videos = [];
this._patch(data);
}
_patch(data) {
if (!data) return;
this.raw = data;
/**
* The channel this playlist is in
* @type {Channel}
*/
this.channel = new Channel(this.youtube, data);
/**
* This playlist's ID
* @type {string}
* @name Playlist#id
*/
switch (data.kind) {
case Constants.KINDS.SearchResult:
if (data.id.kind === Constants.KINDS.Playlist) this.id = data.id.playlistId;
else throw new Error('Attempted to make a playlist out of a non-playlist search result.');
break;
case Constants.KINDS.Playlist:
this.id = data.id;
break;
case Constants.KINDS.PlaylistItem:
if (data.snippet) this.id = data.snippet.playlistId;
else throw new Error('Attempted to make a playlist out of a resource with no playlist data.');
return this; // don't pull extra info from playlist item info
default:
throw new Error(`Unknown playlist kind: ${data.kind}.`);
}
if (data.snippet) {
/**
* This playlist's title
* @type {?string}
*/
this.title = data.snippet.title;
/**
* This playlist's description
* @type {?string}
*/
this.description = data.snippet.description;
/**
* The date/time this playlist was published
* @type {?Date}
*/
this.publishedAt = new Date(data.snippet.publishedAt);
/**
* Thumbnails for this playlist
* @type {?Object.<string, Thumbnail>}
*/
this.thumbnails = data.snippet.thumbnails;
/**
* Channel title of this playlist
* @type {?string}
*/
this.channelTitle = data.snippet.channelTitle;
/**
* The language in this playlist's title and description
* @type {?string}
*/
this.defaultLanguage = data.snippet.defaultLanguage;
/**
* Information about the playlist as specified in the `hl` parameter
* @type {?{title: string, description: string}}
*/
this.localized = data.snippet.localized;
}
if (data.status) {
/**
* The privacy status of this video
* @type {string}
*/
this.privacy = data.status.privacyStatus;
}
if (data.contentDetails) {
/**
* The total number of videos in this playlist
* @type {number}
*/
this.length = data.contentDetails.itemCount;
}
if (data.player) {
/**
* A string with an iframe tag for embedding this playlist
* @type {string}
*/
this.embedHTML = data.player.embedHtml;
}
return this;
}
/**
* The URL to this playlist
* @type {string}
*/
get url() {
return `https://www.youtube.com/playlist?list=${this.id}`;
}
/**
* Fetch the full representation of this playlist.
* @param {object} [options] Any extra query params
* @returns {Playlist}
*/
fetch(options) {
return this.youtube.request.getPlaylist(this.id, options).then(this._patch.bind(this));
}
/**
* Gets videos in the playlist
* @param {Number} [limit] Maximum number of videos to obtain. Fetches all if not provided.
* @param {Object} [options] Options to retrieve for each video.
* @returns {Promise<Video[]>}
*/
getVideos(limit, options) {
return this.youtube.request.getPaginated(
Constants.ENDPOINTS.PlaylistItems,
limit,
Object.assign({ playlistId: this.id, part: Constants.PARTS.PlaylistItems }, options)
).then(items => this.videos = items.map(i => new Video(this.youtube, i)));
}
/**
* Get a playlist ID from a string (URL or ID)
* @param {string} url The string to get the ID from
* @returns {?string}
*/
static extractID(url) {
return parseURL(url).playlist;
}
}
module.exports = Playlist;
+181
View File
@@ -0,0 +1,181 @@
const duration = require('iso8601-duration');
const { parseURL } = require('../util');
const Constants = require('../util/Constants');
const Channel = require('./Channel');
/** Represents a YouTube video */
class Video {
/**
* @param {YouTube} youtube The YouTube instance creating this
* @param {Object} data The data of the video
*/
constructor(youtube, data) {
/**
* The YouTube instance that created this
* @type {YouTube}
*/
this.youtube = youtube;
Object.defineProperty(this, 'youtube', { enumerable: false });
/**
* The type to filter search results
* @type {string}
*/
this.type = 'video';
this._patch(data);
}
_patch(data) {
if (!data) return;
/**
* The raw data from the YouTube API.
* @type {object}
*/
this.raw = data;
/**
* Whether this is a full (returned from the videos API end point) or partial video (returned
* as part of another resource).
* @type {boolean}
*/
this.full = data.kind === Constants.KINDS.Video;
/**
* The resource that this video was created from.
* @type {string}
*/
this.kind = data.kind;
/**
* This video's ID
* @type {string}
* @name Video#id
*/
switch (data.kind) {
case Constants.KINDS.PlaylistItem:
if (data.snippet) {
if (data.snippet.resourceId.kind === Constants.KINDS.Video) this.id = data.snippet.resourceId.videoId;
else throw new Error('Attempted to make a video out of a non-video playlist item.');
break;
} else {
throw new Error('Attempted to make a video out of a playlist item with no video data.');
}
case Constants.KINDS.Video:
this.id = data.id;
break;
case Constants.KINDS.SearchResult:
if (data.id.kind === Constants.KINDS.Video) this.id = data.id.videoId;
else throw new Error('Attempted to make a video out of a non-video search result.');
break;
default:
throw new Error(`Unknown video kind: ${data.kind}.`);
}
if (data.snippet) {
/**
* This video's title
* @type {string}
*/
this.title = data.snippet.title;
/**
* This video's description
* @type {string}
*/
this.description = data.snippet.description;
/**
* The thumbnails of this video.
* @type {Object.<'default', 'medium', 'high', 'standard', 'maxres'>}
*/
this.thumbnails = data.snippet.thumbnails;
/**
* The date/time this video was published
* @type {Date}
*/
this.publishedAt = new Date(data.snippet.publishedAt);
/**
* The channel this video is in.
* @type {Channel}
*/
this.channel = new Channel(this.youtube, data);
}
if(data.contentDetails) {
/**
* An object containing time period information. All properties are integers, and do not include the lower
* precision ones.
* @typedef {Object} DurationObject
* @property {number} [hours] How many hours the video is long
* @property {number} [minutes] How many minutes the video is long
* @property {number} [seconds] How many seconds the video is long
*/
/**
* The duration of the video
* @type {?DurationObject}
*/
this.duration = data.contentDetails.duration ? duration.parse(data.contentDetails.duration) : null;
}
return this;
}
/**
* The maxiumum available resolution thumbnail.
* @type {object}
*/
get maxRes() {
const t = this.thumbnails;
return t.maxres || t.standard || t.high || t.medium || t.default;
}
/**
* The URL to this video
* @type {string}
*/
get url() {
return `https://www.youtube.com/watch?v=${this.id}`;
}
/**
* The short URL to this video
* @type {string}
*/
get shortURL() {
return `https://youtu.be/${this.id}`;
}
/**
* The duration of the video in seconds
* @type {number}
*/
get durationSeconds() {
return this.duration ? duration.toSeconds(this.duration) : -1;
}
/**
* Fetch the full representation of this video.
* @param {object} [options] Any extra query params
* @returns {Video}
*/
fetch(options) {
return this.youtube.request.getVideo(this.id, options).then(this._patch.bind(this));
}
/**
* Get a video ID from a string (URL or ID)
* @param {string} url The string to get the ID from
* @returns {?string}
*/
static extractID(url) {
return parseURL(url).video;
}
}
module.exports = Video;
+23
View File
@@ -0,0 +1,23 @@
exports.PARTS = {
Search: 'snippet',
Videos: 'snippet,contentDetails',
Playlists: 'snippet',
PlaylistItems: 'snippet,status',
Channels: 'snippet'
};
exports.KINDS = {
Video: 'youtube#video',
PlaylistItem: 'youtube#playlistItem',
Playlist: 'youtube#playlist',
SearchResult: 'youtube#searchResult',
Channel: 'youtube#channel'
};
exports.ENDPOINTS = {
PlaylistItems: 'playlistItems',
Channels: 'channels',
Videos: 'videos',
Playlists: 'playlists',
Search: 'search'
};
+36
View File
@@ -0,0 +1,36 @@
const { parse } = require('url');
/**
* Parse a string as a potential YouTube resource URL.
* @param {string} url
* @returns {{video: ?string, channel: ?string, playlist: ?string}}
*/
exports.parseURL = (url) => {
const parsed = parse(url, true);
switch (parsed.hostname) {
case 'www.youtube.com':
case 'youtube.com':
case 'm.youtube.com': {
const idRegex = /^[a-zA-Z0-9-_]+$/;
if (parsed.pathname === '/watch') {
if (!idRegex.test(parsed.query.v)) return {};
const response = { video: parsed.query.v };
if (parsed.query.list) response.playlist = parsed.query.list;
return response;
} else if (parsed.pathname === '/playlist') {
if(!idRegex.test(parsed.query.list)) return {};
return { playlist: parsed.query.list };
} else if (parsed.pathname.startsWith('/channel/')) {
const id = parsed.pathname.replace('/channel/', '');
if (!idRegex.test(id)) return {};
return { channel: id };
}
return {};
}
case 'youtu.be':
return { video: /^\/[a-zA-Z0-9-_]+$/.test(parsed.pathname) ? parsed.pathname.slice(1) : null };
default:
return {};
}
};