Skip to content

feat(YouTube Studio Web): add support for YouTube Studio Web and primarily its uploading capabilities - #1231

Draft
Illusion137 wants to merge 30 commits into
LuanRT:mainfrom
Illusion137:feat(better-uploads)
Draft

feat(YouTube Studio Web): add support for YouTube Studio Web and primarily its uploading capabilities#1231
Illusion137 wants to merge 30 commits into
LuanRT:mainfrom
Illusion137:feat(better-uploads)

Conversation

@Illusion137

@Illusion137 Illusion137 commented Aug 12, 2026

Copy link
Copy Markdown

A well-needed refactored port of my implementation of the YouTube Studio Web client, based off of lib-origin.

Noteable changes and features

  • YouTube Studio Web client
    • uploadVideo()
    • updateVideo()
    • publishVideo()
    • uploadSubtitles()
    • uploadFeedback()
    • ...some other small stuff
  • Updated parser to include the full everything from the full sessionToken route
  • Updated WEB_CREATOR version
  • Added 2 example files to test out the code

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.ts for: "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.ts there are some TODOs regarding the session.context.request.eats, which I'd really like some input of how it should be handled. As far I as know, studio.youtube.com:ytcfg provides an initial EATS, 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

image The uploading example after a successful run (redacted cookies and stuff)

Apologies for the large commits...

…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
Copilot AI lite review requested due to automatic review settings August 12, 2026 12:33

This comment was marked as resolved.

@LuanRT

LuanRT commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Will convert this to a draft since there are likely some things we may want to address first.

@LuanRT
LuanRT marked this pull request as draft August 12, 2026 12:57
Comment thread src/parser/parser.ts Outdated
parsed_data.upload_feedback_items = data.continuationContents
.map((entry) => entry.uploadFeedbackItemContinuation)
.filter((entry) => entry)
.map((entry) => new YTNodes.UploadFeedbackItem(entry));

@LuanRT LuanRT Aug 15, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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...

@LuanRT LuanRT Aug 15, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

@Illusion137 Illusion137 Aug 15, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread src/parser/parser.ts Outdated

_createMemo();
const continuation_contents = data.continuationContents ? parseLC(data.continuationContents) : null;
const continuation_contents = (data.continuationContents && !Array.isArray(data.continuationContents)) ? parseLC(data.continuationContents) : null;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's up with the additional isArray check?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/parser/parser.ts Outdated
}

if (data.translation) {
parsed_data.translation = new YTNodes.Translation(data.translation);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

Comment thread src/parser/parser.ts Outdated
}

// 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 {

@LuanRT LuanRT Aug 15, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably unneeded (does too much)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/parser/parser.ts Outdated
}

if (data?.creatorEntities?.wrappedVideoData?.video) {
parsed_data.creator_video = new YTNodes.CreatorVideo(data.creatorEntities.wrappedVideoData.video);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

@LuanRT
LuanRT self-requested a review August 15, 2026 03:26
Comment thread src/parser/parser.ts Outdated
const parsed_data = {} as T;

const upload_feedback_item = (data.contents && !Array.isArray(data.contents) && data.contents.uploadFeedbackItemRenderer) ?
new YTNodes.UploadFeedbackItem(data.contents.uploadFeedbackItemRenderer) :

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a problem too, since the parser already takes care of contents elsewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't upload videos larger than 2GB Enabling subtitles when uploading a video

3 participants