diff --git a/README.md b/README.md index 034d6ee6..0e42fd9b 100644 --- a/README.md +++ b/README.md @@ -578,6 +578,7 @@ For example, you may want to call an endpoint directly, that can be achieved wit // ... const payload = { + // ... videoId: 'jLTOuvBTLxA', client: 'YTMUSIC', // InnerTube client, can be ANDROID, YTMUSIC, YTMUSIC_ANDROID, WEB or TV_EMBEDDED parse: true // tells YouTube.js to parse the response, this is not sent to InnerTube. @@ -593,10 +594,10 @@ Or maybe there's an interesting `NavigationEndpoint` in a parsed response and we ```ts // ... const artist = await yt.music.getArtist('UC52ZqHVQz5OoGhvbWiRal6g'); -const albums = artist.sections[1].as(MusicCarouselShelf); +const albums = artist.sections[1].as(YTNodes.MusicCarouselShelf); // Say we have a button and want to “click” it -const button = albums.as(MusicCarouselShelf).header?.more_content; +const button = albums.as(YTNodes.MusicCarouselShelf).header?.more_content; if (button) { // To do that, we can call its navigation endpoint: @@ -609,24 +610,11 @@ if (button) { If you're working on an extension for the library or just want to have nicely typed and sanitized InnerTube responses for a project then have a look at our powerful parser! -
-Example: -

- +Example: ```ts // See ./examples/parser -import { Parser } from 'youtubei.js'; - -import SectionList from 'youtubei.js/dist/src/parser/classes/SectionList'; -import SingleColumnBrowseResults from 'youtubei.js/dist/src/parser/classes/SingleColumnBrowseResults'; - -import MusicVisualHeader from 'youtubei.js/dist/src/parser/classes/MusicVisualHeader'; -import MusicImmersiveHeader from 'youtubei.js/dist/src/parser/classes/MusicImmersiveHeader'; -import MusicCarouselShelf from 'youtubei.js/dist/src/parser/classes/MusicCarouselShelf'; -import MusicDescriptionShelf from 'youtubei.js/dist/src/parser/classes/MusicDescriptionShelf'; -import MusicShelf from 'youtubei.js/dist/src/parser/classes/MusicShelf'; - +import { Parser, YTNodes } from 'youtubei.js'; import { readFileSync } from 'fs'; // Artist page response from YouTube Music @@ -634,7 +622,7 @@ const data = readFileSync('./artist.json').toString(); const page = Parser.parseResponse(JSON.parse(data)); -const header = page.header.item().as(MusicImmersiveHeader, MusicVisualHeader); +const header = page.header?.item().as(YTNodes.MusicImmersiveHeader, YTNodes.MusicVisualHeader); console.info('Header:', header); @@ -642,7 +630,8 @@ console.info('Header:', header); // A proxy intercepts access to the actual data, allowing // the parser to add type safety and many utility methods // that make working with InnerTube much easier. -const tab = page.contents.item().as(SingleColumnBrowseResults).tabs.get({ selected: false }); +const tab = page.contents.item().as(YTNodes.SingleColumnBrowseResults).tabs.firstOfType(YTNodes.Tab); + if (!tab) throw new Error('Target tab not found'); @@ -650,14 +639,11 @@ if (!tab) if (!tab.content) throw new Error('Target tab appears to be empty'); -const sections = tab.content?.as(SectionList).contents.array().as(MusicCarouselShelf, MusicDescriptionShelf, MusicShelf); +const sections = tab.content?.as(YTNodes.SectionList).contents.array().as(YTNodes.MusicCarouselShelf, YTNodes.MusicDescriptionShelf, YTNodes.MusicShelf); console.info('Sections:', sections); ``` -

-
- Detailed documentation can be found [here](https://github.com/LuanRT/YouTube.js/blob/main/src/parser). diff --git a/examples/livechat/index.ts b/examples/livechat/index.ts index 47b08d49..b6fa96c3 100644 --- a/examples/livechat/index.ts +++ b/examples/livechat/index.ts @@ -1,31 +1,25 @@ -import { Innertube, UniversalCache } from 'youtubei.js'; +import { Innertube, UniversalCache, YTNodes } from 'youtubei.js'; + import { LiveChatContinuation } from 'youtubei.js/dist/src/parser'; - -import LiveChat, { ChatAction, LiveMetadata } from 'youtubei.js/dist/src/parser/youtube/LiveChat'; - -import Video from 'youtubei.js/dist/src/parser/classes/Video'; -import AddChatItemAction from 'youtubei.js/dist/src/parser/classes/livechat/AddChatItemAction'; -import MarkChatItemAsDeletedAction from 'youtubei.js/dist/src/parser/classes/livechat/MarkChatItemAsDeletedAction'; - -import LiveChatTextMessage from 'youtubei.js/dist/src/parser/classes/livechat/items/LiveChatTextMessage'; -import LiveChatPaidMessage from 'youtubei.js/dist/src/parser/classes/livechat/items/LiveChatPaidMessage'; +import { ChatAction, LiveMetadata } from 'youtubei.js/dist/src/parser/youtube/LiveChat'; (async () => { const yt = await Innertube.create({ cache: new UniversalCache() }); + const search = await yt.search('Lofi girl live'); - const info = await yt.getInfo(search.videos[0].as(Video).id); + const info = await yt.getInfo(search.videos[0].as(YTNodes.Video).id); + + const livechat = info.getLiveChat(); - const livechat = await info.getLiveChat(); - livechat.on('start', (initial_data: LiveChatContinuation) => { /** * Initial info is what you see when you first open a Live Chat — this is; inital actions (pinned messages, top donations..), account's info and so on. */ - + console.info(`Hey ${initial_data.viewer_name || 'N/A'}, welcome to Live Chat!`); }); - + livechat.on('chat-update', (action: ChatAction) => { /** * An action represents what is being added to @@ -35,28 +29,28 @@ import LiveChatPaidMessage from 'youtubei.js/dist/src/parser/classes/livechat/it * Below are a few examples of how this can be used. */ - if (action.is(AddChatItemAction)) { - const item = action.as(AddChatItemAction).item; - + if (action.is(YTNodes.AddChatItemAction)) { + const item = action.as(YTNodes.AddChatItemAction).item; + if (!item) return console.info('Action did not have an item.', action); - + const hours = new Date(item.hasKey('timestamp') ? item.timestamp : Date.now()).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); - + switch (item.type) { case 'LiveChatTextMessage': console.info( - `${hours} - ${item.as(LiveChatTextMessage).author?.name.toString()}:\n` + - `${item.as(LiveChatTextMessage).message.toString()}\n` + `${hours} - ${item.as(YTNodes.LiveChatTextMessage).author?.name.toString()}:\n` + + `${item.as(YTNodes.LiveChatTextMessage).message.toString()}\n` ); break; case 'LiveChatPaidMessage': console.info( - `${hours} - ${item.as(LiveChatPaidMessage).author.name.toString()}:\n` + - `${item.as(LiveChatPaidMessage).purchase_amount}\n` + `${hours} - ${item.as(YTNodes.LiveChatPaidMessage).author.name.toString()}:\n` + + `${item.as(YTNodes.LiveChatPaidMessage).purchase_amount}\n` ); break; default: @@ -64,8 +58,8 @@ import LiveChatPaidMessage from 'youtubei.js/dist/src/parser/classes/livechat/it break; } } - - if (action.is(MarkChatItemAsDeletedAction)) { + + if (action.is(YTNodes.MarkChatItemAsDeletedAction)) { console.warn(`Message ${action.target_item_id} just got deleted and should be replaced with ${action.deleted_state_message.toString()}!`, '\n'); } }); diff --git a/examples/parser/index.ts b/examples/parser/index.ts index 425115b5..50ceaf93 100644 --- a/examples/parser/index.ts +++ b/examples/parser/index.ts @@ -1,22 +1,11 @@ -import { Parser } from 'youtubei.js'; - -import SectionList from 'youtubei.js/dist/src/parser/classes/SectionList'; -import SingleColumnBrowseResults from 'youtubei.js/dist/src/parser/classes/SingleColumnBrowseResults'; - -import MusicVisualHeader from 'youtubei.js/dist/src/parser/classes/MusicVisualHeader'; -import MusicImmersiveHeader from 'youtubei.js/dist/src/parser/classes/MusicImmersiveHeader'; -import MusicCarouselShelf from 'youtubei.js/dist/src/parser/classes/MusicCarouselShelf'; -import MusicDescriptionShelf from 'youtubei.js/dist/src/parser/classes/MusicDescriptionShelf'; -import MusicShelf from 'youtubei.js/dist/src/parser/classes/MusicShelf'; - +import { Parser, YTNodes } from 'youtubei.js'; import { readFileSync } from 'fs'; // Artist page response from YouTube Music const data = readFileSync('./artist.json').toString(); - const page = Parser.parseResponse(JSON.parse(data)); -const header = page.header.item().as(MusicImmersiveHeader, MusicVisualHeader); +const header = page.header?.item().as(YTNodes.MusicImmersiveHeader, YTNodes.MusicVisualHeader); console.info('Header:', header); @@ -24,14 +13,14 @@ console.info('Header:', header); // A proxy intercepts access to the actual data, allowing // the parser to add type safety and many utility methods // that make working with InnerTube much easier. -const tab = page.contents.item().as(SingleColumnBrowseResults).tabs.get({ selected: false }); +const tab = page.contents.item().as(YTNodes.SingleColumnBrowseResults).tabs.firstOfType(YTNodes.Tab); if (!tab) throw new Error('Target tab not found'); if (!tab.content) throw new Error('Target tab appears to be empty'); - -const sections = tab.content?.as(SectionList).contents.array().as(MusicCarouselShelf, MusicDescriptionShelf, MusicShelf); + +const sections = tab.content?.as(YTNodes.SectionList).contents.array().as(YTNodes.MusicCarouselShelf, YTNodes.MusicDescriptionShelf, YTNodes.MusicShelf); console.info('Sections:', sections); \ No newline at end of file