feat(YouTube Studio Web): add support for YouTube Studio Web and primarily its uploading capabilities - #1231
feat(YouTube Studio Web): add support for YouTube Studio Web and primarily its uploading capabilities#1231Illusion137 wants to merge 30 commits into
Conversation
…w Client in Innertube + add support for getting the sessionToken + add a BotGuard interface + add example script for getting YTSW sessionToken with a custom botguard_solver and get_channel_id util
…ts + more managing context + added some uploading related types in `StudioWebUploading.ts` + Add sessionToken caching support + refactoring scotty upload system + `uploadThumbnailResource` and `uploadSubtitles` + a managed execute to handle a lot of the complexity of YouTube Studio Web (sorry big commit)
…id upfront + `updateVideo()`, `publishVideo()`, `uploadFeedbackCycle()`, `uploadFeedback()`, and `uploadVideo()` + update the example to use the new interface + add a wait util to `Utils.ts` Also last commit I forgot to _actually_ cache the sessionToken
…ccidental `parse: true` and make `uploadSubtitles` public
… feat(better-uploads)
…ctually in the data
…ext if exectute throws
|
Will convert this to a draft since there are likely some things we may want to address first. |
… feat(better-uploads)
…the types instead of just assuming types and actually including all the data for all the types that were skimped out on previously
… feat(better-uploads)
…nt just like the rest of them
…sses instead of raw types
… just a single double wrapped creator video
…date_subtitles_response` initially null instead of object to allow to type autocomplete
| parsed_data.upload_feedback_items = data.continuationContents | ||
| .map((entry) => entry.uploadFeedbackItemContinuation) | ||
| .filter((entry) => entry) | ||
| .map((entry) => new YTNodes.UploadFeedbackItem(entry)); |
There was a problem hiding this comment.
If you need to create it manually like this (Which is by itself incorrect and inconsistent with the rest of the parser. Avoid using stuff from the YTNodes map internally), then it's not a real renderer/viewmodel.
The parsing logic so far seems a bit over the place. I can help fix and organize that when I get the time to test this branch.
edit:
also, the multiple iterations aren't good, memory usage wise
There was a problem hiding this comment.
Will refactor this to use parseArray like the rest instead, the issue I was mainly thinking about not duplicating code since /createvideo has uploadFeedbackItemRenderer which is the same type as /feedback's uploadFeedbackItemContinuation...
There was a problem hiding this comment.
I think the biggest problem here is that upload_feedback_items and upload_feedback_item aren't really part of a real InnerTube response. Maybe parseLC should simply be updated to support array of continuations.
YouTube.js' parser is client agnostic. It shouldn't assume anything about any specific client, or care about the shape of the response.
(this is why it hardly ever breaks when yt changes a random prop somewhere, direct prop access is avoided outside of yt nodes and other parser classes).
There was a problem hiding this comment.
Maybe parseLC should simply be updated to support array of continuations
An approach like this does work I guess, but it does break code in a few spots that certain spots that access continuation_contents, moreover I feel like this would break other peoples codebases if something like this was added, so honestly I'm a bit stuck here...
export type ContinuationContents = null | ItemSectionContinuation | SectionListContinuation | LiveChatContinuation |
MusicPlaylistShelfContinuation | MusicShelfContinuation | GridContinuation |
PlaylistPanelContinuation | ContinuationCommand | UploadFeedbackItemContinuation;
export function parseLC(data: RawNode): ContinuationContents
export function parseLC(data: RawNode[]): ContinuationContents[]
export function parseLC(data: RawNode|RawNode[]): ContinuationContents|ContinuationContents[] {
if (!Array.isArray(data)) {
if (data.itemSectionContinuation)
return new ItemSectionContinuation(data.itemSectionContinuation);
if (data.sectionListContinuation)
return new SectionListContinuation(data.sectionListContinuation);
if (data.liveChatContinuation)
return new LiveChatContinuation(data.liveChatContinuation);
if (data.musicPlaylistShelfContinuation)
return new MusicPlaylistShelfContinuation(data.musicPlaylistShelfContinuation);
if (data.musicShelfContinuation)
return new MusicShelfContinuation(data.musicShelfContinuation);
if (data.gridContinuation)
return new GridContinuation(data.gridContinuation);
if (data.playlistPanelContinuation)
return new PlaylistPanelContinuation(data.playlistPanelContinuation);
if (data.continuationCommand)
return new ContinuationCommand(data.continuationCommand);
if (data.uploadFeedbackItemContinuation)
return new YTNodes.UploadFeedbackItem(data.uploadFeedbackItemContinuation);
return null;
}
return data.map((node: RawNode) => parseLC(node));
}edit:
I think actually it's probably better to just add to parsed_data a property called continuation_contents_array or something like that so something this small doesn't change many things?
|
|
||
| _createMemo(); | ||
| const continuation_contents = data.continuationContents ? parseLC(data.continuationContents) : null; | ||
| const continuation_contents = (data.continuationContents && !Array.isArray(data.continuationContents)) ? parseLC(data.continuationContents) : null; |
There was a problem hiding this comment.
What's up with the additional isArray check?
There was a problem hiding this comment.
the /feedback endpoint continuationContents looks a bit weird compared to others, especially since its a array. So the check is to use the code from above to set parsed_data.upload_feedback_items instead of parsed_data.continuation_contents. Well that was the intention, but I suppose yeah, it's unnecessary because of parseLC just returning null in the case. My bad.
| } | ||
|
|
||
| if (data.translation) { | ||
| parsed_data.translation = new YTNodes.Translation(data.translation); |
| } | ||
|
|
||
| // useful for YTStudio since it outputs more in a nested data format rather than a 'rendering' format | ||
| export function parseObject<T = any>(data: unknown): T { |
There was a problem hiding this comment.
Probably unneeded (does too much)
There was a problem hiding this comment.
I think the other stuff is resolved now. For this I'll just go ahead and instead follow the example from src/parser/classes/misc/Format.ts and do all the parsing manually and also strip parts of the very long enums
| } | ||
|
|
||
| if (data?.creatorEntities?.wrappedVideoData?.video) { | ||
| parsed_data.creator_video = new YTNodes.CreatorVideo(data.creatorEntities.wrappedVideoData.video); |
| const parsed_data = {} as T; | ||
|
|
||
| const upload_feedback_item = (data.contents && !Array.isArray(data.contents) && data.contents.uploadFeedbackItemRenderer) ? | ||
| new YTNodes.UploadFeedbackItem(data.contents.uploadFeedbackItemRenderer) : |
There was a problem hiding this comment.
This is a problem too, since the parser already takes care of contents elsewhere.
…the parser to use what already exists instead
…rip useless long info off enums
…g just like the actual requests
A well-needed refactored port of my implementation of the YouTube Studio Web client, based off of lib-origin.
Noteable changes and features
uploadVideo()updateVideo()publishVideo()uploadSubtitles()uploadFeedback()Testing
I've added two example files that are easy enough to fill in:
examples/youtube-studio-web/src/GetSessionToken.ts- setups a BotGuard solver and grabs and logs your YouTube Session Token; important since this was really the only thing that was blocking using this client before.examples/youtube-studio-web/src/UploadVideo.ts- same setup, but then uploads a video with a custom thumbnail and synced subtitles with 2 different progress callbacks.Final Notes
I left a TODO in
src/types/BotGuard.tsfor: "make this more comprehensive" for the AttestationBinding. I think it might be better to have an interface like that, but currently it is kind of like a placeholder if the BotGuardSolver interface eventually expands.Lastly, in
src/core/Actions.tsthere are some TODOs regarding thesession.context.request.eats, which I'd really like some input of how it should be handled. As far I as know,studio.youtube.com:ytcfgprovides an initialEATS, but it doesn't seem like it changes anything to have or not to have. Because of this I don't know if it's truly necessary to have an additional network request for nothing.Fixes #307, #414, #418, #1164
Likely fixes #616
Apologies for the large commits...