Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use YouTube.js to fetch history #302

Merged
merged 1 commit into from
Jul 30, 2024
Merged
Changes from all 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
49 changes: 13 additions & 36 deletions src/yt-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
headers.append('X-YouTube-Client-Version', config.INNERTUBE_CONTEXT_CLIENT_VERSION)
headers.append('X-YouTube-Device', config.DEVICE)
headers.append('X-YouTube-Identity-Token', config.ID_TOKEN)
init.headers = headers;

Check failure on line 41 in src/yt-api.ts

View workflow job for this annotation

GitHub Actions / Build

Assignment to property of function parameter 'init'
return fetch(input, init);
}
});
Expand All @@ -46,7 +46,7 @@
const response = await youtube.session.http.fetch(`/playlist?list=${playlistName}&pbj=1`, {baseURL: 'https://www.youtube.com'});
const data = (await response.json()).response.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content
.sectionListRenderer.contents[0].itemSectionRenderer.contents[0].playlistVideoListRenderer;

Check failure on line 49 in src/yt-api.ts

View workflow job for this annotation

GitHub Actions / Build

Do not access a member directly from an await expression
if (!data) {
throw PlaylistEmptyError
}
Expand All @@ -64,7 +64,7 @@
): Promise<PlaylistContinuation> {
const youtube = await Innertube.create({
cookie: document.cookie,
fetch: (...args) => fetch(...args),

Check failure on line 67 in src/yt-api.ts

View workflow job for this annotation

GitHub Actions / Build

The variable `args` should be named `arguments_`. A more descriptive name will do too
})

const body = {continuation: continuation.continuationToken}
Expand Down Expand Up @@ -97,46 +97,23 @@
throw PlaylistNotEditableError
}

async function getRemoveFromHistoryToken(videoId: string): Promise<string> {
const initDataRegex = /(?:window\["ytInitialData"]|ytInitialData)\W?=\W?({.*?});/
const result = await fetch('https://www.youtube.com/feed/history', {
credentials: 'include',
method: 'GET',
mode: 'cors',
async function getFeedbackToken(videoId: string): Promise<string | undefined> {
const youtube = await Innertube.create({
cookie: document.cookie,
fetch: (...args) => fetch(...args),
})
const body = await result.text()

try {
const matchedData = body.match(initDataRegex)
if (!matchedData || !matchedData[1]) throw new Error('Failed to parse initData')
const initData = JSON.parse(matchedData[1])

const groups =
initData?.contents?.twoColumnBrowseResultsRenderer?.tabs?.[0]?.tabRenderer?.content?.sectionListRenderer?.contents
.map((group: { itemSectionRenderer: object }) => group.itemSectionRenderer)
.filter(Boolean)

let matchingVideo
for (const item of groups) {
for (const { videoRenderer } of item.contents) {
if (videoRenderer?.videoId && videoId === videoRenderer?.videoId) {
matchingVideo = videoRenderer
break
}
}
}
const history = await youtube.getHistory()

if (!matchingVideo) {
throw new Error('Video not found in watch history')
for (const section of history.sections) {
for (const content of section.contents) {
if (content.hasKey('id') && content.id === videoId) {
return content.hasKey('menu') && content.menu.top_level_buttons[0].endpoint.payload.feedbackToken
}
}

return matchingVideo?.menu?.menuRenderer?.topLevelButtons?.[0]?.buttonRenderer?.serviceEndpoint?.feedbackEndpoint
?.feedbackToken
} catch (error) {
// eslint-disable-next-line no-console
console.log(error)
throw new Error('Failed to parse initData')
}

throw new Error('No token found in watch history')
}

async function sendFeedbackRequest(feedbackToken: string) {
Expand All @@ -155,7 +132,7 @@
}

export async function removeWatchHistoryForVideo(videoId: string) {
const feedbackToken = await getRemoveFromHistoryToken(videoId)
const feedbackToken = await getFeedbackToken(videoId)
if (feedbackToken) {
await sendFeedbackRequest(feedbackToken)
}
Expand All @@ -165,7 +142,7 @@
playlistId: string,
videosToRemove: PlaylistVideo[],
): Promise<boolean> {
const youtube = await Innertube.create({

Check failure on line 145 in src/yt-api.ts

View workflow job for this annotation

GitHub Actions / Build

The variable `args` should be named `arguments_`. A more descriptive name will do too
cookie: document.cookie,
fetch: (...args) => fetch(...args),
})
Expand Down
Loading