Skip to content
This repository has been archived by the owner on Sep 20, 2023. It is now read-only.

Don't error when tabs are missing #94

Merged
merged 8 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ ytch.getChannelInfo(payload).then((response) => {
isOfficialArtist: Boolean,
tags: Array[String], // Will return null if none exist
channelIdType: Number,
channelTabs: Array[String], // The tabs that are displayed on the channel (e.g., Videos, Playlists)
alertMessage: String, // Will return a response alert message if any (e.g., "This channel does not exist."). Otherwise undefined
channelLinks: {
primaryLinks: Array[Object],
Expand Down
25 changes: 20 additions & 5 deletions app/fetchers/playlist.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const YoutubeGrabberHelper = require('../helper')
const helper = require('../helper')

class PlaylistFetcher {
Expand All @@ -24,19 +25,33 @@ class PlaylistFetcher {
if (typeof (channelPageDataResponse) === 'undefined') {
channelPageDataResponse = response.data[1].response
}
const channelMetaData = channelPageDataResponse.metadata.channelMetadataRenderer
const channelName = channelMetaData.title
const channelId = channelMetaData.externalId
if (typeof (channelPageDataResponse.alerts) !== 'undefined') {
return {
alertMessage: channelPageDataResponse.alerts[0].alertRenderer.text.simpleText
}
}
let channelName
let channelMetaData
let channelId
if ('metadata' in channelPageDataResponse) {
channelMetaData = channelPageDataResponse.metadata.channelMetadataRenderer
channelName = channelMetaData.title
channelId = channelMetaData.externalId
}
const ytGrabHelp = helper.create(httpAgent)

const channelInfo = {
channelId: channelId,
channelName: channelName,
channelUrl: `https://www.youtube.com/channel/${channelId}`
}
let playlistData
const playlistTab = YoutubeGrabberHelper.findTab(channelPageDataResponse.contents.twoColumnBrowseResultsRenderer.tabs)

const playlistData = channelPageDataResponse.contents.twoColumnBrowseResultsRenderer.tabs[2].tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0].gridRenderer

if (playlistTab !== undefined) {
const tabRenderer = playlistTab.tabRenderer
playlistData = tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0].gridRenderer
}
if (typeof (playlistData) === 'undefined') {
return {
continuation: null,
Expand Down
40 changes: 32 additions & 8 deletions app/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,18 @@ class YoutubeGrabberHelper {
alertMessage: channelPageDataResponse.alerts[0].alertRenderer.text.simpleText
}
}
let channelMetaData
let channelName
if ('metadata' in channelPageDataResponse) {
channelMetaData = channelPageDataResponse.metadata.channelMetadataRenderer
channelName = channelMetaData.title
}
const videoTab = YoutubeGrabberHelper.findTab(channelPageDataResponse.contents.twoColumnBrowseResultsRenderer.tabs)

const channelMetaData = channelPageDataResponse.metadata.channelMetadataRenderer
const channelName = channelMetaData.title
const channelVideoData = channelPageDataResponse.contents.twoColumnBrowseResultsRenderer.tabs[1].tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0].gridRenderer

let channelVideoData
if (videoTab !== undefined) {
channelVideoData = channelPageDataResponse.contents.twoColumnBrowseResultsRenderer.tabs[1].tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0].gridRenderer
}
if (typeof (channelVideoData) === 'undefined') {
// Channel has no videos
return {
Expand Down Expand Up @@ -116,7 +123,7 @@ class YoutubeGrabberHelper {
const channelUrl = author.navigationEndpoint.browseEndpoint.canonicalBaseUrl
const thumbnail = author.thumbnail.thumbnails
let videoCount = 0
if ('videoCout' in author) {
if ('videoCountText' in author) {
videoCount = author.videoCountText.runs[0].text
}
let subscriberText
Expand Down Expand Up @@ -294,9 +301,20 @@ class YoutubeGrabberHelper {

// Parse the JSON data and get the relevent array with data
let contentDataJSON = JSON.parse(contentDataString)
contentDataJSON = contentDataJSON.contents.twoColumnBrowseResultsRenderer.tabs[3].tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer
if ('continuationItemRenderer' in contentDataJSON.contents[contentDataJSON.contents.length - 1]) {
return { items: this.createCommunityPostArray(contentDataJSON.contents), continuation: contentDataJSON.contents[contentDataJSON.contents.length - 1].continuationItemRenderer.continuationEndpoint.continuationCommand.token, innerTubeApi: innertubeAPIkey, channelIdType: channelIdType }
if (typeof (contentDataJSON.alerts) !== 'undefined') {
return {
alertMessage: contentDataJSON.alerts[0].alertRenderer.text.simpleText
}
}
const communityTab = YoutubeGrabberHelper.findTab(contentDataJSON.contents.twoColumnBrowseResultsRenderer.tabs)

if (communityTab) {
contentDataJSON = contentDataJSON.contents.twoColumnBrowseResultsRenderer.tabs[3].tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer
if ('continuationItemRenderer' in contentDataJSON.contents[contentDataJSON.contents.length - 1]) {
return { items: this.createCommunityPostArray(contentDataJSON.contents), continuation: contentDataJSON.contents[contentDataJSON.contents.length - 1].continuationItemRenderer.continuationEndpoint.continuationCommand.token, innerTubeApi: innertubeAPIkey, channelIdType: channelIdType }
}
} else {
contentDataJSON = { contents: [] }
}
return { items: this.createCommunityPostArray(contentDataJSON.contents), continuation: null, innerTubeApi: null, channelIdType: channelIdType }
}
Expand Down Expand Up @@ -669,6 +687,12 @@ class YoutubeGrabberHelper {
return { response: channelPageResponse, channelIdType: 3 }
}

static findTab(tab) {
return tab.find((data) =>
data?.tabRenderer?.selected === true
ChunkyProgrammer marked this conversation as resolved.
Show resolved Hide resolved
)
}

static create(httpsAgent) {
return new YoutubeGrabberHelper(httpsAgent)
}
Expand Down
78 changes: 43 additions & 35 deletions app/youtube-grabber.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ class YoutubeGrabber {
if (channelPageResponse.data.response === undefined) {
channelPageDataResponse = channelPageResponse.data[1].response
}
const headerLinks = channelPageDataResponse.header.c4TabbedHeaderRenderer.headerLinks
let headerLinks
if ('c4TabbedHeaderRenderer' in channelPageDataResponse.header) {
headerLinks = channelPageDataResponse.header.c4TabbedHeaderRenderer.headerLinks
}
const links = {
primaryLinks: [],
secondaryLinks: []
Expand Down Expand Up @@ -58,17 +61,15 @@ class YoutubeGrabber {
const channelMetaData = channelPageDataResponse.metadata.channelMetadataRenderer
const channelHeaderData = channelPageDataResponse.header.c4TabbedHeaderRenderer
const headerTabs = channelPageDataResponse.contents.twoColumnBrowseResultsRenderer.tabs
const channelTabs = headerTabs
.filter(tab => tab.tabRenderer !== undefined && tab.tabRenderer !== null)
.map(tab => tab.tabRenderer.title)

const channelsTab = headerTabs.filter((data) => {
if (typeof data.tabRenderer !== 'undefined') {
return data.tabRenderer.title === 'Channels'
}

return false
})

const featuredChannels = channelsTab[0].tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0]

const channelsTab = YoutubeGrabberHelper.findTab(headerTabs)
let featuredChannels = {}
if (channelsTab !== undefined) {
featuredChannels = channelsTab.tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0]
}
let relatedChannels = []
let relatedChannelsContinuation = null

Expand Down Expand Up @@ -152,6 +153,7 @@ class YoutubeGrabber {
isOfficialArtist: isOfficialArtist,
tags: tags,
channelLinks: links,
channelTabs: channelTabs,
channelIdType: decideResponse.channelIdType,
}

Expand Down Expand Up @@ -334,6 +336,11 @@ class YoutubeGrabber {
if (typeof channelPageDataResponse === 'undefined') {
channelPageDataResponse = channelPageResponse.data[1].response
}
if (typeof (channelPageDataResponse.alerts) !== 'undefined') {
return {
alertMessage: channelPageDataResponse.alerts[0].alertRenderer.text.simpleText
}
}
const channelMetaData = channelPageDataResponse.metadata.channelMetadataRenderer
const channelName = channelMetaData.title

Expand Down Expand Up @@ -457,28 +464,28 @@ class YoutubeGrabber {
const ytGrabHelp = YoutubeGrabberHelper.create(httpAgent)
const decideResponse = await ytGrabHelp.decideUrlRequestType(channelId, 'about?flow=grid&view=0&pbj=1', channelIdType)
const channelPageResponse = decideResponse.response
let headerTabs
if (channelPageResponse.data.response) {
headerTabs = channelPageResponse.data.response.contents.twoColumnBrowseResultsRenderer.tabs
} else {
headerTabs = channelPageResponse.data[1].response.contents.twoColumnBrowseResultsRenderer.tabs
}
const aboutTab = headerTabs.filter((data) => {
if (typeof data.tabRenderer !== 'undefined') {
return data.tabRenderer.title === 'About'
const channelPageDataResponse = channelPageResponse.data[1].response
if (typeof (channelPageDataResponse.alerts) !== 'undefined') {
return {
alertMessage: channelPageDataResponse.alerts[0].alertRenderer.text.simpleText
}
return false
})[0]
const contents = aboutTab.tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0]
const joined = Date.parse(contents.channelAboutFullMetadataRenderer.joinedDateText.runs[1].text)
}
const headerTabs = channelPageDataResponse.contents.twoColumnBrowseResultsRenderer.tabs
const aboutTab = YoutubeGrabberHelper.findTab(headerTabs)

let views = '0'
let location = 'unknown'
if ('viewCountText' in contents.channelAboutFullMetadataRenderer) {
views = contents.channelAboutFullMetadataRenderer.viewCountText.simpleText.replace(/\D/g, '')
}
let joined = null
if (aboutTab !== undefined) {
const contents = aboutTab.tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0]
joined = Date.parse(contents.channelAboutFullMetadataRenderer.joinedDateText.runs[1].text)
if ('viewCountText' in contents.channelAboutFullMetadataRenderer) {
views = contents.channelAboutFullMetadataRenderer.viewCountText.simpleText.replace(/\D/g, '')
}

if ('country' in contents.channelAboutFullMetadataRenderer) {
location = contents.channelAboutFullMetadataRenderer.country.simpleText
if ('country' in contents.channelAboutFullMetadataRenderer) {
location = contents.channelAboutFullMetadataRenderer.country.simpleText
}
}

return {
Expand All @@ -500,6 +507,11 @@ class YoutubeGrabber {
if (typeof channelPageDataResponse === 'undefined') {
channelPageDataResponse = channelPageResponse.data[1].response
}
if (typeof (channelPageDataResponse.alerts) !== 'undefined') {
return {
alertMessage: channelPageDataResponse.alerts[0].alertRenderer.text.simpleText
}
}
const headerTabs = channelPageDataResponse.contents.twoColumnBrowseResultsRenderer.tabs
let channelName
let channelUrl
Expand All @@ -517,13 +529,9 @@ class YoutubeGrabber {
channelName: channelName,
channelUrl: channelUrl
}
const homeTab = headerTabs.filter((data) => {
if (typeof data.tabRenderer !== 'undefined') {
return data.tabRenderer.title === 'Home'
}

return false
})[0]
const homeTab = YoutubeGrabberHelper.findTab(headerTabs)

let featuredVideo = null
let homeItems = []
if (homeTab !== undefined) {
Expand Down
11 changes: 11 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,18 @@ declare module "yt-channel-info" {
isOfficialArtist: boolean;
tags: string[];
channelIdType: number;
channelTabs: string[];
alertMessage: string;
channelLinks: {
primaryLinks: ChannelLink[],
secondaryLinks: ChannelLink[]
}
}

interface ChannelLink {
url: string,
icon: string,
title: string
}

/**
Expand Down
12 changes: 12 additions & 0 deletions test/channelHome.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,16 @@ describe('Getting channel home', () => {
expect(data.items.length).not.toBe(0)
})
})
test('Rammstein - topic', () => {
const parameters = { channelId: 'UCs6GGpd9zvsYghuYe0VDFUQ', channelIdType: 1 }
return ytch.getChannelHome(parameters).then((data) => {
expect(data.items.length).not.toBe(0)
})
})
test('Deleted channel', () => {
const parameters = { channelId: 'UC59AcfHD5jOGqTxb-zAsahw', channelIdType: 1 }
return ytch.getChannelHome(parameters).then((data) => {
expect(data.alertMessage).not.toBe(undefined)
})
})
})
13 changes: 13 additions & 0 deletions test/channelInfo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,17 @@ describe('Getting channel info', () => {
expect(data.alertMessage).toBe('This channel does not exist.')
})
})

test('Channel missing tabs', () => {
const parameters = { channelId: 'UCs6GGpd9zvsYghuYe0VDFUQ', channelIdType: 1 }
return ytch.getChannelInfo(parameters).then((data) => {
expect(data.channelTabs.length).toBe(3)
})
})
test('Deleted channel', () => {
const parameters = { channelId: 'UC59AcfHD5jOGqTxb-zAsahw', channelIdType: 1 }
return ytch.getChannelVideos(parameters).then((data) => {
expect(data.alertMessage).not.toBe(undefined)
})
})
})
13 changes: 13 additions & 0 deletions test/channelPlaylists.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,17 @@ describe('Playlists', () => {
expect(data.items.length).toBe(0)
})
})
test('Channel missing playlist tab', () => {
const parameters = { channelId: 'UCYfdidRxbB8Qhf0Nx7ioOYw', channelIdType: 1 }
return ytch.getChannelPlaylistInfo(parameters).then((data) => {
expect(data.items.length).toBe(0)
expect(data.continuation).toBe(null)
})
})
test('Deleted channel', () => {
const parameters = { channelId: 'UC59AcfHD5jOGqTxb-zAsahw', channelIdType: 1 }
return ytch.getChannelPlaylistInfo(parameters).then((data) => {
expect(data.alertMessage).not.toBe(undefined)
})
})
})
6 changes: 6 additions & 0 deletions test/channelStats.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,10 @@ describe('Channel stats', () => {
expect(data.joinedDate).toBeLessThanOrEqual(1355227200000 + 86400000)
})
})
test('Deleted channel', () => {
const parameters = { channelId: 'UC59AcfHD5jOGqTxb-zAsahw', channelIdType: 1 }
return ytch.getChannelStats(parameters).then((data) => {
expect(data.alertMessage).not.toBe(undefined)
})
})
})
14 changes: 14 additions & 0 deletions test/channelVideos.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,18 @@ describe('Getting channel videos', () => {
expect(data.items.length).toBeGreaterThan(0)
})
})

test('Channel missing video tab', () => {
const parameters = { channelId: 'UCs6GGpd9zvsYghuYe0VDFUQ', channelIdType: 1 }
return ytch.getChannelVideos(parameters).then((data) => {
expect(data.items.length).toBe(0)
expect(data.continuation).toBe(null)
})
})
test('Deleted channel', () => {
const parameters = { channelId: 'UC59AcfHD5jOGqTxb-zAsahw', channelIdType: 1 }
return ytch.getChannelVideos(parameters).then((data) => {
expect(data.alertMessage).not.toBe(undefined)
})
})
})
6 changes: 6 additions & 0 deletions test/communityPosts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ describe('Community Posts', () => {
})
})
})
test('Deleted channel', () => {
const parameters = { channelId: 'UC59AcfHD5jOGqTxb-zAsahw', channelIdType: 1 }
return ytch.getChannelCommunityPosts(parameters).then((data) => {
expect(data.alertMessage).not.toBe(undefined)
})
})
})
6 changes: 6 additions & 0 deletions test/searchChannel.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ describe('Searching channels', () => {
})
})
})
test('Deleted channel', () => {
const parameters = { channelId: 'UC59AcfHD5jOGqTxb-zAsahw', channelIdType: 1 }
return ytch.getChannelVideos(parameters).then((data) => {
expect(data.alertMessage).not.toBe(undefined)
})
})
})