From 5ea0a0ebf8fe5e0731d8bbdd08cdc50bad069a6b Mon Sep 17 00:00:00 2001 From: LuanRT Date: Sat, 12 Nov 2022 16:26:02 -0300 Subject: [PATCH] feat: add support for switching accounts (cookie based auth only) (#236) * feat: add support for switching accounts * style: lint --- src/core/AccountManager.ts | 6 +++--- src/core/Actions.ts | 2 +- src/core/InteractionManager.ts | 14 +++++++------- src/core/PlaylistManager.ts | 10 +++++----- src/core/Session.ts | 25 ++++++++++++++++++++----- src/core/Studio.ts | 6 +++--- src/utils/HTTPClient.ts | 3 +++ 7 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/core/AccountManager.ts b/src/core/AccountManager.ts index 70b06cd5..d5991325 100644 --- a/src/core/AccountManager.ts +++ b/src/core/AccountManager.ts @@ -21,7 +21,7 @@ class AccountManager { */ editName: (new_name: string) => { if (!this.#actions.session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); return this.#actions.execute('/channel/edit_name', { givenName: new_name, @@ -34,7 +34,7 @@ class AccountManager { */ editDescription: (new_description: string) => { if (!this.#actions.session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); return this.#actions.execute('/channel/edit_description', { givenDescription: new_description, @@ -53,7 +53,7 @@ class AccountManager { */ async getInfo() { if (!this.#actions.session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); const response = await this.#actions.execute('/account/accounts_list', { client: 'ANDROID' }); return new AccountInfo(response); diff --git a/src/core/Actions.ts b/src/core/Actions.ts index cfb2f6aa..c455a00d 100644 --- a/src/core/Actions.ts +++ b/src/core/Actions.ts @@ -122,7 +122,7 @@ class Actions { if (Reflect.has(data, 'browseId')) { if (this.#needsLogin(data.browseId) && !this.#session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); } if (Reflect.has(data, 'override_endpoint')) diff --git a/src/core/InteractionManager.ts b/src/core/InteractionManager.ts index f646422b..13ea48c6 100644 --- a/src/core/InteractionManager.ts +++ b/src/core/InteractionManager.ts @@ -17,7 +17,7 @@ class InteractionManager { throwIfMissing({ video_id }); if (!this.#actions.session.logged_in) - throw new Error('You are not signed in'); + throw new Error('You must be signed in to perform this operation.'); const action = await this.#actions.execute('/like/like', { client: 'ANDROID', @@ -37,7 +37,7 @@ class InteractionManager { throwIfMissing({ video_id }); if (!this.#actions.session.logged_in) - throw new Error('You are not signed in'); + throw new Error('You must be signed in to perform this operation.'); const action = await this.#actions.execute('/like/dislike', { client: 'ANDROID', @@ -57,7 +57,7 @@ class InteractionManager { throwIfMissing({ video_id }); if (!this.#actions.session.logged_in) - throw new Error('You are not signed in'); + throw new Error('You must be signed in to perform this operation.'); const action = await this.#actions.execute('/like/removelike', { client: 'ANDROID', @@ -77,7 +77,7 @@ class InteractionManager { throwIfMissing({ channel_id }); if (!this.#actions.session.logged_in) - throw new Error('You are not signed in'); + throw new Error('You must be signed in to perform this operation.'); const action = await this.#actions.execute('/subscription/subscribe', { client: 'ANDROID', @@ -96,7 +96,7 @@ class InteractionManager { throwIfMissing({ channel_id }); if (!this.#actions.session.logged_in) - throw new Error('You are not signed in'); + throw new Error('You must be signed in to perform this operation.'); const action = await this.#actions.execute('/subscription/unsubscribe', { client: 'ANDROID', @@ -116,7 +116,7 @@ class InteractionManager { throwIfMissing({ video_id, text }); if (!this.#actions.session.logged_in) - throw new Error('You are not signed in'); + throw new Error('You must be signed in to perform this operation.'); const action = await this.#actions.execute('/comment/create_comment', { client: 'ANDROID', @@ -163,7 +163,7 @@ class InteractionManager { throwIfMissing({ channel_id, type }); if (!this.#actions.session.logged_in) - throw new Error('You are not signed in'); + throw new Error('You must be signed in to perform this operation.'); const pref_types = { PERSONALIZED: 1, diff --git a/src/core/PlaylistManager.ts b/src/core/PlaylistManager.ts index d875132b..362c3f29 100644 --- a/src/core/PlaylistManager.ts +++ b/src/core/PlaylistManager.ts @@ -20,7 +20,7 @@ class PlaylistManager { throwIfMissing({ title, video_ids }); if (!this.#actions.session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); const response = await this.#actions.execute('/playlist/create', { title, @@ -44,7 +44,7 @@ class PlaylistManager { throwIfMissing({ playlist_id }); if (!this.#actions.session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); const response = await this.#actions.execute('playlist/delete', { playlistId: playlist_id }); @@ -65,7 +65,7 @@ class PlaylistManager { throwIfMissing({ playlist_id, video_ids }); if (!this.#actions.session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); const response = await this.#actions.execute('/browse/edit_playlist', { playlistId: playlist_id, @@ -91,7 +91,7 @@ class PlaylistManager { throwIfMissing({ playlist_id, video_ids }); if (!this.#actions.session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); const info = await this.#actions.execute('/browse', { browseId: `VL${playlist_id}`, @@ -150,7 +150,7 @@ class PlaylistManager { throwIfMissing({ playlist_id, moved_video_id, predecessor_video_id }); if (!this.#actions.session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); const info = await this.#actions.execute('/browse', { browseId: `VL${playlist_id}`, diff --git a/src/core/Session.ts b/src/core/Session.ts index d0de3d68..91d6d642 100644 --- a/src/core/Session.ts +++ b/src/core/Session.ts @@ -56,6 +56,7 @@ export interface Context { export interface SessionOptions { lang?: string; + account_index?: number; device_category?: DeviceCategory; client_type?: ClientType; timezone?: string; @@ -68,6 +69,7 @@ export default class Session extends EventEmitterLike { #api_version; #key; #context; + #account_index; #player; oauth; @@ -76,9 +78,10 @@ export default class Session extends EventEmitterLike { actions; cache; - constructor(context: Context, api_key: string, api_version: string, player: Player, cookie?: string, fetch?: FetchFunction, cache?: UniversalCache) { + constructor(context: Context, api_key: string, api_version: string, account_index: number, player: Player, cookie?: string, fetch?: FetchFunction, cache?: UniversalCache) { super(); this.#context = context; + this.#account_index = account_index; this.#key = api_key; this.#api_version = api_version; this.#player = player; @@ -107,12 +110,20 @@ export default class Session extends EventEmitterLike { } static async create(options: SessionOptions = {}) { - const { context, api_key, api_version } = await Session.getSessionData(options.lang, options.device_category, options.client_type, options.timezone, options.fetch); - return new Session(context, api_key, api_version, await Player.create(options.cache, options.fetch), options.cookie, options.fetch, options.cache); + const { context, api_key, api_version, account_index } = await Session.getSessionData( + options.lang, + options.account_index, + options.device_category, + options.client_type, + options.timezone, + options.fetch + ); + return new Session(context, api_key, api_version, account_index, await Player.create(options.cache, options.fetch), options.cookie, options.fetch, options.cache); } static async getSessionData( lang = 'en-US', + account_index = 0, device_category: DeviceCategory = 'desktop', client_name: ClientType = ClientType.WEB, tz: string = Intl.DateTimeFormat().resolvedOptions().timeZone, @@ -177,7 +188,7 @@ export default class Session extends EventEmitterLike { } }; - return { context, api_key, api_version }; + return { context, api_key, api_version, account_index }; } async signIn(credentials?: Credentials): Promise { @@ -213,7 +224,7 @@ export default class Session extends EventEmitterLike { async signOut() { if (!this.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); const response = await this.oauth.revokeCredentials(); this.logged_in = false; @@ -237,6 +248,10 @@ export default class Session extends EventEmitterLike { return this.#context.client.clientName; } + get account_index() { + return this.#account_index; + } + get context() { return this.#context; } diff --git a/src/core/Studio.ts b/src/core/Studio.ts index e4f1a1ef..7e5b2a12 100644 --- a/src/core/Studio.ts +++ b/src/core/Studio.ts @@ -52,7 +52,7 @@ class Studio { */ async setThumbnail(video_id: string, buffer: Uint8Array): Promise { if (!this.#session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); if (!video_id || !buffer) throw new MissingParamError('One or more parameters are missing.'); @@ -83,7 +83,7 @@ class Studio { */ async updateVideoMetadata(video_id: string, metadata: VideoMetadata) { if (!this.#session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); const payload = Proto.encodeVideoMetadataPayload(video_id, metadata); @@ -105,7 +105,7 @@ class Studio { */ async upload(file: BodyInit, metadata: UploadedVideoMetadata = {}): Promise { if (!this.#session.logged_in) - throw new InnertubeError('You are not signed in'); + throw new InnertubeError('You must be signed in to perform this operation.'); const initial_data = await this.#getInitialUploadData(); const upload_result = await this.#uploadVideo(initial_data.upload_url, file); diff --git a/src/utils/HTTPClient.ts b/src/utils/HTTPClient.ts index 796a4978..467ee03c 100644 --- a/src/utils/HTTPClient.ts +++ b/src/utils/HTTPClient.ts @@ -103,9 +103,12 @@ export default class HTTPClient { if (this.#cookie) { const papisid = getStringBetweenStrings(this.#cookie, 'PAPISID=', ';'); + if (papisid) { request_headers.set('authorization', await generateSidAuth(papisid)); + request_headers.set('x-goog-authuser', this.#session.account_index.toString()); } + request_headers.set('cookie', this.#cookie); } }