@freetube/yt-comment-scraper
Advanced tools
Comparing version 5.2.1 to 6.0.0
module.exports = require("./src/Youtube-Scraper") |
{ | ||
"name": "@freetube/yt-comment-scraper", | ||
"version": "5.2.1", | ||
"version": "6.0.0", | ||
"description": "Scrapes the comments of any YouTube video without YouTube API access. Uses the default YouTube Ajax calls to get the comment data.", | ||
@@ -28,7 +28,19 @@ "main": "index.js", | ||
], | ||
"author": "GilgusMaximus <software@lucahohmann.com>", | ||
"author": { | ||
"name": "GilgusMaximus", | ||
"email": "software@lucahohmann.com" | ||
}, | ||
"contributors": [ | ||
{ | ||
"name": "Svallinn", | ||
"email": "svallinn@protonmail.com" | ||
}, | ||
{ | ||
"name": "PrestonN", | ||
"email": "FreeTubeApp@protonmail.com" | ||
} | ||
], | ||
"license": "GPLv3", | ||
"dependencies": { | ||
"axios": "^0.21.1", | ||
"node-html-parser": "^2.0.2" | ||
"axios": "^0.21.1" | ||
}, | ||
@@ -35,0 +47,0 @@ "devDependencies": { |
@@ -29,4 +29,4 @@ # YouTube Comment Scraper NodeJS Documentation | ||
- continuation (String) (Optional) - The token from a previous call to continue grabbing comments | ||
- setCookie (Boolean) (Optional) - The flag should be set to true when cookies are not handled by your application (e.g. Electron) already | ||
- httpsAgent (Object) (Optional) - Allows to specify all kind of different agent data (see NodeJS [documentation](https://nodejs.org/api/https.html#https_class_https_agent) or 3rd party packages like [node-https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) for options like proxies). | ||
- mustSetCookie (Boolean) (Optional) - The flag should be set to true when cookies are not handled by your application (e.g. Electron) already | ||
- httpsAgent (Object) (Optional) - Allows to specify all kind of different agent data (see NodeJS [documentation](https://nodejs.org/api/https.html#https_class_https_agent) or 3rd party packages like [node-https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) for options like proxies) | ||
```javascript | ||
@@ -42,3 +42,3 @@ const https = require('https'); | ||
continuation: continuation, | ||
setCookie: false, | ||
mustSetCookie: false, | ||
httpsAgent: agent | ||
@@ -55,31 +55,34 @@ } | ||
The data is returned as a list of objects (seen below). | ||
The data is returned as an object with a list of comment objects and a continuation token (if more comments exist) | ||
```javascript | ||
// The data is a list of objects containing the following attributes: | ||
{ | ||
comments: [ | ||
{ | ||
commentId: String, // Id of comment | ||
authorId: String, // Id of user that made the comment | ||
author: String, // Name of the channel that made the comment | ||
authorThumb: Array [ // An Array of thumbnails of the channel profile | ||
{ | ||
width: Number, | ||
height: Number, | ||
url: String | ||
} | ||
], | ||
edited: Boolean, // If the comment has been edited or not | ||
text: String, // The text content of the comment | ||
likes: String, // The amount of likes the comment has, numbers > 1000 displayed with 1.9K, 2K... | ||
time: String, // The time the comment was published. Written as "One day ago" | ||
numReplies: Number, // The number of replies found for the comment | ||
isOwner: Boolean, // If the video channel made the comment | ||
isHearted: Boolean, // If the video channel hearted the comment | ||
isPinned: Boolean, // If the video channel pinned the comment | ||
isVerified: Boolean, | ||
isOfficialArtist: Boolean, | ||
hasOwnerReplied: Boolean, // If the video channel replied to the comment | ||
replyToken: String // The continuation token needed for getCommentReplies() | ||
}], | ||
continuation: String // The continuation token needed to get more comments from getComments() | ||
{ | ||
commentId: String, // Id of comment | ||
authorId: String, // Id of user that made the comment | ||
author: String, // Name of the channel that made the comment | ||
authorThumb: Array [ // An Array of thumbnails of the channel profile | ||
{ | ||
width: Number, | ||
height: Number, | ||
url: String | ||
} | ||
], | ||
edited: Boolean, // If the comment has been edited or not | ||
text: String, // The text content of the comment | ||
likes: String, // The amount of likes the comment has, numbers > 1000 displayed with 1.9K, 2K... | ||
time: String, // The time the comment was published. Written as "One day ago" | ||
numReplies: Number, // The number of replies found for the comment | ||
isOwner: Boolean, // If the video channel made the comment | ||
isHearted: Boolean, // If the video channel hearted the comment | ||
isPinned: Boolean, // If the video channel pinned the comment | ||
isVerified: Boolean, | ||
isOfficialArtist: Boolean, | ||
hasOwnerReplied: Boolean, // If the video channel replied to the comment | ||
replyToken: String // The continuation token needed for getCommentReplies() | ||
} | ||
], | ||
continuation: String | null // The continuation token needed to get more comments from getComments() | ||
} | ||
``` | ||
@@ -91,8 +94,9 @@ | ||
- payload (Object) (Required) - An object containing the various options | ||
- videoId (String) (Required) - The video ID to grab comments from | ||
- replyToken (String) (Required) - The reply token from a comment object of `getComments()` or the continuation string from a previous call to `getCommentReplies()` | ||
- setCookie (Boolean) (Optional) - The flag should be set to true when cookies are not handled by your application already (e.g. Electron) | ||
- httpsAgent (Object) (Optional) - As seen before | ||
- mustSetCookie (Boolean) (Optional) - The flag should be set to true when cookies are not handled by your application (e.g. Electron) already | ||
- httpsAgent (Object) (Optional) - Allows to specify all kind of different agent data (see NodeJS [documentation](https://nodejs.org/api/https.html#https_class_https_agent) or 3rd party packages like [node-https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) for options like proxies) | ||
```javascript | ||
const parameters = {videoId: 'someId', replyToken: 'HSDcjasgdajwSdhAsd', setCookie: true, httpsAgent: null}; | ||
const parameters = {videoId: 'someId', replyToken: 'HSDcjasgdajwSdhAsd', mustSetCookie: true, httpsAgent: null}; | ||
ytcm.getCommentReplies(parameters).then((data) =>{ | ||
@@ -134,5 +138,5 @@ console.log(data); | ||
}], | ||
continuation: String // The continuation token needed (instead of replyToken) to get more replies from getCommentReplies() | ||
continuation: String | null // The continuation token needed (instead of replyToken) to get more replies from getCommentReplies() | ||
``` | ||
## Credits | ||
Thanks to egbertbouman for his/her Python [project](https://github.com/egbertbouman/youtube-comment-downloader) which guided this project through the difficult HTTP calls. |
@@ -1,5 +0,2 @@ | ||
const parser = require('node-html-parser') | ||
class HtmlParser { | ||
static parseCommentData(data) { | ||
@@ -9,6 +6,5 @@ const comments = [] | ||
data.forEach((node) => { | ||
if ('continuationItemRenderer' in node) return | ||
const comment = node.commentThreadRenderer ? node.commentThreadRenderer.comment.commentRenderer : node.commentRenderer | ||
let replies = null | ||
let text = '' | ||
let isHearted = false | ||
@@ -19,4 +15,4 @@ const commentId = comment.commentId | ||
const authorThumbnails = comment.authorThumbnail.thumbnails | ||
const likes = ('voteCount' in comment) ? comment.voteCount.simpleText.split(' ')[0] : '0' | ||
const numReplies = comment.replyCount ? comment.replyCount : 0 | ||
const likes = comment.voteCount?.simpleText?.split(' ')[0] ?? '0' | ||
const numReplies = comment.replyCount ?? 0 | ||
const publishedTimeText = comment.publishedTimeText.runs[0].text | ||
@@ -26,9 +22,8 @@ const publishedText = publishedTimeText.replace('(edited)', '').trim() | ||
const heartBadge = comment.actionButtons.commentActionButtonsRenderer.creatorHeart | ||
const isOwner = comment.authorIsChannelOwner | ||
const isPinned = comment.pinnedCommentBadge ? true : false | ||
const isVerified = ('authorCommentBadge' in comment && comment.authorCommentBadge.authorCommentBadgeRenderer.icon.iconType === "CHECK_CIRCLE_THICK") | ||
const isOfficialArtist = ('authorCommentBadge' in comment && comment.authorCommentBadge.authorCommentBadgeRenderer.icon.iconType === "OFFICIAL_ARTIST_BADGE") | ||
const isPinned = 'pinnedCommentBadge' in comment | ||
const isVerified = comment.authorCommentBadge?.authorCommentBadgeRenderer?.icon?.iconType === "CHECK_CIRCLE_THICK" | ||
const isOfficialArtist = comment.authorCommentBadge?.authorCommentBadgeRenderer?.icon?.iconType === "OFFICIAL_ARTIST_BADGE" | ||
const isHearted = comment.actionButtons.commentActionButtonsRenderer.creatorHeart?.creatorHeartRenderer?.isHearted ?? false | ||
if (typeof heartBadge !== 'undefined') { | ||
@@ -48,3 +43,3 @@ isHearted = heartBadge.creatorHeartRenderer.isHearted | ||
const object = { | ||
const commentData = { | ||
authorThumb: authorThumbnails, | ||
@@ -70,12 +65,13 @@ author: authorName, | ||
const replyNode = node.commentThreadRenderer.replies.commentRepliesRenderer | ||
const continuation = replyNode.continuations[0].nextContinuationData.continuation | ||
object.replyToken = continuation | ||
const continuation = replyNode.contents[0].continuationItemRenderer.continuationEndpoint.continuationCommand.token | ||
commentData.replyToken = continuation | ||
const replyArrayLength = replyNode.viewReplies.buttonRenderer.text.runs.length | ||
//lengths of: 1 = reply (not from owner), 2 = reply (from owner), 3 = replies (not from owner), 5 = replies (from owmer) | ||
if (replyArrayLength == 5 || replyArrayLength == 2){ | ||
object.hasOwnerReplied = true; | ||
// lengths of: 1 = reply (not from owner), 2 = reply (from owner), 3 = replies (not from owner), 5 = replies (from owmer) | ||
if (replyArrayLength === 2 || replyArrayLength === 5) { | ||
commentData.hasOwnerReplied = true | ||
} | ||
} | ||
comments.push(object) | ||
comments.push(commentData) | ||
}) | ||
@@ -86,8 +82,8 @@ | ||
static parseShortedNumberString(string) { | ||
const numberMultiplier = string.charAt(string.length-1).toLowerCase() | ||
switch (numberMultiplier){ | ||
const numberMultiplier = string.charAt(string.length - 1).toLowerCase() | ||
switch (numberMultiplier) { | ||
case 'k': | ||
return Number(string.substring(0, string.length-1) * 1000) | ||
return Number(string.substring(0, string.length - 1) * 1000) | ||
case 'm': | ||
return Number(string.substring(0, string.length-1) * 1000000) | ||
return Number(string.substring(0, string.length - 1) * 1000000) | ||
} | ||
@@ -94,0 +90,0 @@ } |
const axios = require("axios") | ||
const baseURL = "https://www.youtube.com/" | ||
const ajaxURL = "comment_service_ajax" | ||
const baseURL = "https://www.youtube.com" | ||
// Generates random integer (start and end inclusive) | ||
function random(start, end) { | ||
return Math.floor(Math.random() * (end - start + 1)) + start | ||
} | ||
class HttpRequester { | ||
constructor(setCookie = false, httpsAgent = null) { | ||
this.setCookie = setCookie | ||
this.session = axios.create({ | ||
baseURL: baseURL, | ||
timeout: 10000, | ||
headers: { | ||
'X-YouTube-Client-Name': '1', | ||
'X-YouTube-Client-Version': '2.20210331.06.00', | ||
'accept-language': 'en-US,en;q=0.5', | ||
}, | ||
httpsAgent: httpsAgent | ||
}) | ||
// NOTE: This currently provides a CONSENT cookie to | ||
// everyone, including non-European populations, | ||
// making this cookie potentially fingerprintable | ||
if(this.setCookie) { | ||
this.session.defaults.headers.cookie = [`CONSENT=YES+cb.20210328-17-p0.en+FX+${random(100, 999)}`] | ||
} | ||
constructor(mustSetCookie, httpsAgent) { | ||
this.mustSetCookie = mustSetCookie | ||
this.session = axios.create({ | ||
baseURL: baseURL, | ||
timeout: 10000, | ||
headers: { | ||
'accept-language': 'en-US,en;q=0.5', | ||
}, | ||
httpsAgent: httpsAgent | ||
}) | ||
// NOTE: This currently provides a CONSENT cookie to | ||
// everyone, including non-European populations, | ||
// making this cookie potentially fingerprintable | ||
if (this.mustSetCookie) { | ||
this.session.defaults.headers.cookie = [`CONSENT=YES+`] | ||
} | ||
} | ||
async getVideoTokens(videoId, sortBy='top') { | ||
try { | ||
const response = await this.session.get(baseURL+ "watch?v=" + videoId) | ||
const html_data = response.data | ||
const pre_token = html_data.match(/"XSRF_TOKEN":"[^"]*"/)[0] | ||
static async create(videoId, mustSetCookie, httpsAgent) { | ||
const requester = new HttpRequester(mustSetCookie, httpsAgent) | ||
let initialResponse | ||
try { | ||
initialResponse = await requester.session.get(`/watch?v=${videoId}`) | ||
} catch { | ||
return { | ||
error: true, | ||
message: `Unable to reach ${baseURL}/watch?v=${videoId}` | ||
} | ||
} | ||
// token embedded in page, needed for ajax request | ||
let xsrf = pre_token.substring(14, pre_token.length-1) | ||
xsrf = xsrf.replace(/\\u003d/g, "=") | ||
const html_data = initialResponse.data | ||
let continuation = html_data.match(/"nextContinuationData":{"continuation":"(.*?)}/)[0] | ||
continuation = JSON.parse(`{${continuation}}`) | ||
// Cache data in the requester for future use | ||
requester.cachedInitialData = html_data | ||
let continuationToken = continuation.nextContinuationData.continuation | ||
const ytConfigSelectors = [ | ||
/"INNERTUBE_CONTEXT_CLIENT_NAME":(\d*)/, // X-YouTube-Client-Name | ||
/"INNERTUBE_CONTEXT_CLIENT_VERSION":"([^"]*)"/, // X-YouTube-Client-Version | ||
] | ||
if (sortBy === 'new') { | ||
const letterContinuationList = { | ||
Q: "T", | ||
w: "z", | ||
A: "D" | ||
} | ||
let serializedToken, letterContinuation | ||
try { | ||
let serializedShareEntity = html_data.match(/"serializedShareEntity":(.*?)}/)[0] | ||
serializedShareEntity = JSON.parse(`{${serializedShareEntity}`) | ||
serializedToken = serializedShareEntity.serializedShareEntity.replace(/\w%3D%3D/, '') | ||
letterContinuation = serializedShareEntity.serializedShareEntity.replace(/%3D%3D/, '') | ||
} catch { | ||
let getTranscriptEndpointParams = html_data.match(/"getTranscriptEndpoint":{"params":"(.*?)}/)[0] | ||
getTranscriptEndpointParams = JSON.parse(`{${getTranscriptEndpointParams}}`) | ||
serializedToken = getTranscriptEndpointParams.getTranscriptEndpoint.params.replace(/\w%3D%3D/, '') | ||
letterContinuation = getTranscriptEndpointParams.getTranscriptEndpoint.params.replace(/%3D%3D/, '') | ||
} | ||
serializedToken = serializedToken.replace(/C{1}/, '') | ||
letterContinuation = letterContinuationList[letterContinuation.slice(-1)] | ||
continuationToken = continuationToken.replace('%3D', '') + `yFSIRI${serializedToken}${letterContinuation}ABeAIwAA%3D%3D` | ||
} | ||
if (this.setCookie) { | ||
for (const cookie of response.headers["set-cookie"]) { | ||
const prunedCookie = cookie.match(/([A-Z0-9_]+=[^;]+);.+/)[1] | ||
this.session.defaults.headers['cookie'].push(prunedCookie) | ||
} | ||
} | ||
const ytConfigData = html_data.match( | ||
new RegExp(ytConfigSelectors.map(r => r.source).join(".*")) | ||
) | ||
return { | ||
xsrf: xsrf, | ||
continuation: continuationToken | ||
} | ||
} catch (e) { | ||
return { | ||
error: true, | ||
message: e | ||
} | ||
} | ||
// Set YouTube specific headers for the subsequent requests | ||
requester.session.defaults.headers = { | ||
...requester.session.defaults.headers, | ||
'X-YouTube-Client-Name': ytConfigData[1], | ||
'X-YouTube-Client-Version': ytConfigData[2] | ||
} | ||
async requestCommentsPage(payload){ | ||
let endpoint | ||
if (payload.useReplyEndpoint) { | ||
endpoint = 'action_get_comment_replies' | ||
} else { | ||
endpoint = 'action_get_comments' | ||
} | ||
return requester | ||
} | ||
const urlParams = new URLSearchParams(); | ||
urlParams.append(endpoint, '1') | ||
urlParams.append('pbj', '1') | ||
urlParams.append('ctoken', payload.page_token) | ||
urlParams.append('continuation', payload.page_token) | ||
getContinuationToken(sortByNewest) { | ||
let continuation = this.cachedInitialData.match( | ||
/"itemSectionRenderer".*"token":"([^"]*)".*"targetId":"comments-section"/ | ||
)[1] | ||
// This variable is necessary in order to post the data | ||
// as raw body and not as object | ||
const urlBodyParams = new URLSearchParams(); | ||
urlBodyParams.append('session_token', payload.session_token) | ||
// Gets top comments by default, and new comments if true | ||
if (sortByNewest) { | ||
continuation = continuation.slice(0, 47) + "B" + continuation.substring(48) | ||
} | ||
return await this.session.post( | ||
`${ajaxURL}?${urlParams.toString()}`, | ||
urlBodyParams | ||
) | ||
return continuation | ||
} | ||
async requestCommentsPage(continuation) { | ||
const payload = { | ||
context: { | ||
client: { | ||
clientName: 'WEB', | ||
clientVersion: this.session.defaults.headers['X-YouTube-Client-Version'] | ||
} | ||
}, | ||
continuation | ||
} | ||
return await this.session.post( | ||
`/youtubei/v1/next?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8`, | ||
payload | ||
) | ||
} | ||
} | ||
module.exports = HttpRequester |
const HttpRequester = require("./HttpRequester") | ||
const htmlParser = require('./htmlParser') | ||
class CommentScraper { | ||
static async getComments(payload) { | ||
if (typeof payload.videoId === 'undefined') { | ||
return Promise.reject('No video Id given') | ||
} | ||
const ValidationId = { | ||
getComments: 0, | ||
getCommentReplies: 1 | ||
} | ||
let xsrf | ||
let continuationToken | ||
const sortBy = payload.sortByNewest ? 'new' : 'top' | ||
const requester = new HttpRequester((payload.setCookie === true), payload.httpsAgent) | ||
function validateArgs(id, payload) { | ||
// Common properties | ||
if (typeof payload.videoId !== 'string') { | ||
throw new TypeError('videoId is required and must be of type "string"') | ||
} | ||
if (typeof payload.mustSetCookie !== 'boolean') payload.mustSetCookie = false | ||
if (typeof payload.httpsAgent !== 'object') payload.httpsAgent = null | ||
if (payload.continuation) { | ||
if (typeof payload.xsrf !== 'undefined') { | ||
xsrf = payload.xsrf | ||
} else { | ||
const tokens = await requester.getVideoTokens(payload.videoId, sortBy) | ||
xsrf = tokens.xsrf | ||
} | ||
continuationToken = payload.continuation | ||
} else { | ||
const tokens = await requester.getVideoTokens(payload.videoId, sortBy, (payload.setCookie === true)) | ||
xsrf = tokens.xsrf | ||
continuationToken = tokens.continuation | ||
} | ||
// Specific properties | ||
switch (id) { | ||
case ValidationId.getComments: | ||
if (typeof payload.sortByNewest !== 'boolean') payload.sortByNewest = false | ||
if (typeof payload.continuation !== 'string') payload.continuation = null | ||
return payload; | ||
const commentsPayload = { | ||
session_token: xsrf, | ||
page_token: continuationToken, | ||
useReplyEndpoint: false | ||
case ValidationId.getCommentReplies: | ||
if (typeof payload.replyToken !== 'string') { | ||
throw new TypeError('replyToken is required and must be of type "string"') | ||
} | ||
return payload; | ||
} | ||
} | ||
class CommentScraper { | ||
static async getComments(payload) { | ||
const { videoId, sortByNewest, continuation, mustSetCookie, httpsAgent } = | ||
validateArgs(ValidationId.getComments, payload) | ||
const commentPageResponse = await requester.requestCommentsPage(commentsPayload) | ||
const commentHtml = commentPageResponse.data.response.continuationContents.itemSectionContinuation | ||
const commentData = (typeof commentHtml.contents !== 'undefined') ? htmlParser.parseCommentData(commentHtml.contents) : [] | ||
const continuation = commentHtml.continuations | ||
const requester = await HttpRequester.create(videoId, mustSetCookie, httpsAgent) | ||
if (requester.error) { | ||
throw new Error(requester.message) | ||
} | ||
let ctoken = null | ||
let token = continuation ?? requester.getContinuationToken(sortByNewest) | ||
const commentPageResponse = await requester.requestCommentsPage(token) | ||
let commentHtml | ||
if (continuation) { | ||
commentHtml = commentPageResponse.data.onResponseReceivedEndpoints[0].appendContinuationItemsAction | ||
} else { | ||
commentHtml = commentPageResponse.data.onResponseReceivedEndpoints[1].reloadContinuationItemsCommand | ||
} | ||
if (typeof continuation !== 'undefined') { | ||
ctoken = continuation[0].nextContinuationData.continuation | ||
} | ||
// Reset to return new token back to caller (or null, in case it doesn't exist) | ||
token = null | ||
return { | ||
comments: commentData, | ||
continuation: ctoken | ||
let commentData = [] | ||
if ('continuationItems' in commentHtml) { | ||
commentData = htmlParser.parseCommentData(commentHtml.continuationItems) | ||
const continuationElem = commentHtml.continuationItems[commentHtml.continuationItems.length - 1] | ||
if ('continuationItemRenderer' in continuationElem) { | ||
token = continuationElem.continuationItemRenderer.continuationEndpoint.continuationCommand.token | ||
} | ||
} | ||
static async getCommentReplies({videoId, replyToken, setCookie, httpsAgent}) { | ||
if (typeof videoId === 'undefined') { | ||
return Promise.reject('No video Id given') | ||
} | ||
return { comments: commentData, continuation: token } | ||
} | ||
const requester = new HttpRequester((setCookie === true), httpsAgent) | ||
static async getCommentReplies(payload) { | ||
const { videoId, replyToken, mustSetCookie, httpsAgent } = | ||
validateArgs(ValidationId.getCommentReplies, payload) | ||
const tokens = await requester.getVideoTokens(videoId, 'top') | ||
const xsrf = tokens.xsrf | ||
const requester = await HttpRequester.create(videoId, mustSetCookie, httpsAgent) | ||
if (requester.error) { | ||
throw new Error(requester.message) | ||
} | ||
const commentsPayload = { | ||
session_token: xsrf, | ||
page_token: replyToken, | ||
useReplyEndpoint: true | ||
} | ||
const commentPageResponse = await requester.requestCommentsPage(replyToken) | ||
const commentHtml = commentPageResponse.data.onResponseReceivedEndpoints[0].appendContinuationItemsAction | ||
const commentData = htmlParser.parseCommentData(commentHtml.continuationItems) | ||
const commentPageResponse = await requester.requestCommentsPage(commentsPayload) | ||
const commentHtml = commentPageResponse.data[1].response.continuationContents.commentRepliesContinuation | ||
const commentData = htmlParser.parseCommentData(commentHtml.contents) | ||
const continuations = commentHtml.continuations | ||
let token = null | ||
const continuationElem = commentHtml.continuationItems[commentHtml.continuationItems.length - 1] | ||
if ('continuationItemRenderer' in continuationElem) { | ||
token = continuationElem.continuationItemRenderer.button.buttonRenderer.command.continuationCommand.token | ||
} | ||
let ctoken = null | ||
if (typeof continuations !== 'undefined') { | ||
ctoken = continuations[0].nextContinuationData.continuation | ||
} | ||
return { | ||
comments: commentData, | ||
continuation: ctoken | ||
} | ||
} | ||
return { comments: commentData, continuation: token } | ||
} | ||
} | ||
module.exports = CommentScraper |
@@ -8,5 +8,5 @@ const ytcm = require('../index') | ||
test('Scrape top video comments of first page', () => { | ||
const parameters = {videoId: 'oBLQmE-nG60', setCookie: true, sortByNewest: false}; | ||
const parameters = {videoId: 'oBLQmE-nG60', mustSetCookie: true, sortByNewest: false}; | ||
return ytcm.getComments(parameters).then((data) => { | ||
expect(data.comments).toHaveLength(20); | ||
expect(data.comments).not.toHaveLength(0); | ||
}); | ||
@@ -16,5 +16,5 @@ }); | ||
test('Scrape newest video comments of first page', () => { | ||
const parameters = {videoId: 'oBLQmE-nG60', setCookie: true, sortByNewest: true}; | ||
const parameters = {videoId: 'oBLQmE-nG60', mustSetCookie: true, sortByNewest: true}; | ||
return ytcm.getComments(parameters).then((data) => { | ||
expect(data.comments).toHaveLength(20); | ||
expect(data.comments).not.toHaveLength(0); | ||
}); | ||
@@ -24,5 +24,5 @@ }); | ||
test('Scrape newest video comments second page', () => { | ||
let parameters = {videoId: 'oBLQmE-nG60', setCookie: true, sortByNewest: true, continuation: null}; | ||
let parameters = {videoId: 'oBLQmE-nG60', mustSetCookie: true, sortByNewest: true, continuation: null}; | ||
return ytcm.getComments(parameters).then((data) => { | ||
parameters["continuation"] = data.continuation; | ||
parameters.continuation = data.continuation; | ||
ytcm.getComments(parameters).then((data) => { | ||
@@ -35,3 +35,3 @@ expect(data.comments).not.toHaveLength(0); | ||
test('Scrape top replies of first page', () => { | ||
const parameters = {videoId: 'oBLQmE-nG60', setCookie: true, sortByNewest: false}; | ||
const parameters = {videoId: 'oBLQmE-nG60', mustSetCookie: true, sortByNewest: false}; | ||
// This test first gets comments of the video with above Id | ||
@@ -42,5 +42,5 @@ return ytcm.getComments(parameters).then((data) => { | ||
if (data.comments[i].numReplies > 0) { | ||
const replyParameters = {videoId: 'oBLQmE-nG60', replyToken: data.comments[i].replyToken, setCookie: true}; | ||
const replyParameters = {videoId: 'oBLQmE-nG60', replyToken: data.comments[i].replyToken, mustSetCookie: true}; | ||
return ytcm.getCommentReplies(replyParameters).then((replyData) => { | ||
expect(replyData.comments).toHaveLength(10); | ||
expect(replyData.comments).not.toHaveLength(0); | ||
}); | ||
@@ -53,3 +53,3 @@ } | ||
test('Scrape second batch of top replies of first page', () => { | ||
const parameters = {videoId: 'oBLQmE-nG60', setCookie: true, sortByNewest: false}; | ||
const parameters = {videoId: 'oBLQmE-nG60', mustSetCookie: true, sortByNewest: false}; | ||
// This test first gets comments of the video with above Id | ||
@@ -60,3 +60,3 @@ return ytcm.getComments(parameters).then((data) => { | ||
if (data.comments[i].numReplies > 0) { | ||
let replyParameters = {videoId: 'oBLQmE-nG60', replyToken: data.comments[i].replyToken, setCookie: true}; | ||
let replyParameters = {videoId: 'oBLQmE-nG60', replyToken: data.comments[i].replyToken, mustSetCookie: true}; | ||
return ytcm.getCommentReplies(replyParameters).then((replyData) => { | ||
@@ -74,3 +74,3 @@ replyParameters.replyToken = replyData.continuation; | ||
test('Scrape video without comments', () => { | ||
const parameters = {videoId: 'Bj-3M-KqZsI', setCookie: true, sortByNewest: false}; | ||
const parameters = {videoId: 'Bj-3M-KqZsI', mustSetCookie: true, sortByNewest: false}; | ||
return ytcm.getComments(parameters).then((data) => { | ||
@@ -77,0 +77,0 @@ expect(data.comments).toHaveLength(0); |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
1
138
55668
289
2
- Removednode-html-parser@^2.0.2
- Removedhe@1.2.0(transitive)
- Removednode-html-parser@2.2.1(transitive)