New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@freetube/yt-comment-scraper

Package Overview
Dependencies
Maintainers
2
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@freetube/yt-comment-scraper - npm Package Compare versions

Comparing version 5.2.1 to 6.0.0

0

index.js
module.exports = require("./src/Youtube-Scraper")

20

package.json
{
"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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc