Skip to content

Commit

Permalink
feat(youtube)!: Use oauth-based api library for better stability
Browse files Browse the repository at this point in the history
* Fixes #195 missing (404) history endpoint which only appear when using cookies
* Might fix #158 due to using a different authentication method
  • Loading branch information
FoxxMD committed Oct 11, 2024
1 parent 5e95f60 commit 72d79fc
Show file tree
Hide file tree
Showing 15 changed files with 5,726 additions and 249 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ dist
.pnp.*

config/*.json
config/yti-*
*.txt
.idea/

Expand Down
2 changes: 0 additions & 2 deletions config/ytmusic.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
"enable": true,
"clients": [],
"data": {
"cookie": "VISITOR_INFO1_LIVE=jMDXz2_L8rY; __Secure-3PAPISID=3AxsXpSXGqOInSDn1jEKn; DEVICE_INFO=ChxOekU0TmTBpjek5EWZ0G; YSC=7gZdl3Zdl3; SID=TwhNsaZRXYTAtXxzGyu6rZdpg2HvGROeW8J4Ym_FhkhoZMUYEQ.; __Secure-1PSID=TwhNOsaZRXYTyRBe4rxAtXRIKsIEtk_Qot2VLBNfHQrQ.; __Secure-3PSID=ZRXYTAtXRIKsIEtk_Qot2yRBerZdpg2HvvZRXYTAtXRIKsIEtk_Qot2yRBerkuZICFQ.; HSID=A1UMmELW79; SSID=AKhomOs; APISID=IlHHmuzkPdQzZZDhHn3; SAPISID=3AxsXpy0u75Qb/n1jEKn; __Secure-1PAPISID=3AxsXpQb/AkSDn1jEKn; LOGIN_INFO=AFmP6vFpyVCZZAIgDwbkhWMBBhluaIWAPP:QUQ314UW5NWMjNmd2ZUJnYnJsakdIMjZoaE5zVVMjNmd2ZZUiHRlb3ZlV3ZIcUVyRVIMjNmdjNmd2ZZUivYlNqX2ZNZUiHdUNFNFdaYmJIW1NkJRX3hqdlU2YnFESkFuSS1uTldnZVRmLXNjWFc5OUJuR3dTd3JsZGZYa2EtZFQ2a0k2Ry1KQQ==; PREF=volume=26; SIDCC=AFvI_94PxXwls-ndqpGfPgFX3FWj80y_94PxXwls-ndqfSh15sP; __Secure-1PSIDCC=AFvIBnUbRr96I96UCIp2U4T8HRVk2B0HfKzhzxwsiP; __Secure-3PSIDCC=AFvIB3bINuUN0ETDR9gO91wpwWIVmpGki3BxT3bINuUN0ETDR9gO91wCH",
"authUser": 0
}
}
]
28 changes: 15 additions & 13 deletions docsite/docs/configuration/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -576,28 +576,30 @@ After starting multi-scrobbler with credentials in-place open the dashboard (`ht

### [Youtube Music](https://music.youtube.com)

<details>

<summary>Migrating from YT Music cookie-based Source</summary>

In multi-scrobbler **below v0.9.0** YT Music credentials were extracted from browser cookies. Due to authentication inconsistency and YT service changes this was approach was dropped in favor of [oauth authentication which is more stable.](https://ytjs.dev/guide/authentication.html#youtube-tv-oauth2)

Your existing credentials cannot be migrated. However, the oauth approach is quite easy. Continue following the directions below to setup new authentication for your YT Music Source.

</details>

:::note

* YT Music authentication is "browser based" which means your credentials may expire after a (long?) period of time OR if you log out of https://music.youtube.com. In the event this happens just repeat the steps below to get new credentials. [See the FAQ](../FAQ.md#youtube-music-fails-after-some-time) for a more detailed explanation.
* Communication to YT Music is **unofficial** and not supported or endorsed by Google. This means that **this integration may stop working at any time** if Google decides to change how YT Music works in the browser.
* Due to this scrobble history from YTM is often inconsistent and can cause missed scrobbles. [See the FAQ](../FAQ.md#youtube-music-misses-scrobbles) for a more detailed explanation.

:::

Credentials for YT Music are obtained from a browser request to https://music.youtube.com **once you are logged in.** [Specific requirements are here and summarized below:](https://github.com/nickp10/youtube-music-ts-api/blob/master/DOCUMENTATION.md#authenticate)

* Open a new tab
* Open the developer tools (Ctrl-Shift-I) and select the “Network” tab
* Go to https://music.youtube.com and ensure you are logged in

Then...
To authenticate simply start multi-scrobbler with an empty YT Music configuration. An authentication URL/code will be logged in additon to being available from the dashboard.

1. Find and select an authenticated POST request. The simplest way is to filter by /browse using the search bar of the developer tools. If you don’t see the request, try scrolling down a bit or clicking on the library button in the top bar.
2. **Make sure **Headers** pane is selected and open
3. In the **Request Headers** section find and copy the **entire value** found after `Cookie:` and use this as the `cookie` value in your multi-scrobbler config
4. If present, in the **Request Headers** section find and copy the number found in `X-google-AuthUser` and use this as the value for `authUser` in your multi-scrobbler config
```
[2024-10-09 15:24:17.358 -0400] INFO : [App] [Sources] [Ytmusic - MyYTM] ERROR: Sign in with the code 'CLV-KFA-BVKY' using the authentication link on the dashboard or https://www.google.com/device
```

![Google Headers](google-header.jpg)
Visit the authentication URL and enter the code that was provided (also available on the dashboard). After completing the setup flow MS will log `Auth success` and the YT Music dashboard card will display as **Idle** after refreshing. Click the **Start** link to begin monitoring.

#### Configuration

Expand Down
Binary file removed docsite/docs/configuration/google-header.jpg
Binary file not shown.
81 changes: 47 additions & 34 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
"vite-express": "^0.16.0",
"vlc-client": "^1.1.1",
"xml2js": "0.6.0",
"youtube-music-ts-api": "^1.7.0"
"youtubei.js": "^10.5.0"
},
"devDependencies": {
"@dbus-types/notifications": "^0.0.5",
Expand Down
68 changes: 0 additions & 68 deletions patches/youtube-music-ts-api+1.7.0.patch

This file was deleted.

4 changes: 4 additions & 0 deletions src/backend/common/AbstractComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,4 +318,8 @@ export default abstract class AbstractComponent {
return play;
}
}

public additionalApiData(): Record<string, any> {
return {};
}
}
25 changes: 4 additions & 21 deletions src/backend/common/infrastructure/config/source/ytmusic.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,15 @@
import { PollingOptions } from "../common.js";
import { CommonSourceConfig, CommonSourceData, CommonSourceOptions } from "./index.js";

export interface YTMusicCredentials {
/**
* The cookie retrieved from the Request Headers of music.youtube.com after logging in.
*
* See https://github.com/nickp10/youtube-music-ts-api/blob/master/DOCUMENTATION.md#authenticate and https://ytmusicapi.readthedocs.io/en/latest/setup.html#copy-authentication-headers for how to retrieve this value.
*
* @examples ["VISITOR_INFO1_LIVE=jMp2xA1Xz2_PbVc; __Secure-3PAPISID=3AxsXpy0M/AkISpjek; ..."]
* */
cookie: string
/**
* If the 'X-Goog-AuthUser' header is present in the Request Headers for music.youtube.com it must also be included
*
* @example [0]
* */
authUser?: number | string
}

export interface YTMusicData extends YTMusicCredentials, CommonSourceData, PollingOptions {
export interface YTMusicData extends CommonSourceData, PollingOptions {
}
export interface YTMusicSourceConfig extends CommonSourceConfig {
data: YTMusicData
data?: YTMusicData
options?: CommonSourceOptions & {
/**
* When true MS will log to DEBUG what parts of the cookie are updated by YTM
* When true MS will log to DEBUG all of the credentials data it receives from YTM
* */
logAuthUpdateChanges?: boolean
logAuth?: boolean
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/backend/server/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, appLoggerStream:
authed,
players: 'players' in x ? (x as MemorySource).playersToObject() : {},
sot: ('playerSourceOfTruth' in x) ? x.playerSourceOfTruth : SOURCE_SOT.HISTORY,
supportsUpstreamRecentlyPlayed: x.supportsUpstreamRecentlyPlayed
supportsUpstreamRecentlyPlayed: x.supportsUpstreamRecentlyPlayed,
...x.additionalApiData()
};
if(!x.isReady()) {
if(x.buildOK === false) {
Expand Down
5 changes: 5 additions & 0 deletions src/backend/server/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ScrobbleClients from "../scrobblers/ScrobbleClients.js";
import LastfmSource from "../sources/LastfmSource.js";
import ScrobbleSources from "../sources/ScrobbleSources.js";
import SpotifySource from "../sources/SpotifySource.js";
import YTMusicSource from "../sources/YTMusicSource.js";

export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMiddle: ExpressHandler, clientMiddle: ExpressHandler, scrobbleSources: ScrobbleSources, scrobbleClients: ScrobbleClients) => {
app.use('/api/client/auth', clientMiddle);
Expand Down Expand Up @@ -49,6 +50,10 @@ export const setupAuthRoutes = (app: ExpressWithAsync, logger: Logger, sourceMid
// @ts-expect-error TS(2339): Property 'deezerSource' does not exist on type 'Se... Remove this comment to see the full error message
req.session.deezerSource = name;
return passport.authenticate(`deezer-${source.name}`)(req,res,next);
case 'ytmusic':
await (source as YTMusicSource).reauthenticate();
res.redirect((source as YTMusicSource).verificationUrl);
break;
default:
return res.status(400).send(`Specified source does not have auth implemented (${source.type})`);
}
Expand Down
Loading

0 comments on commit 72d79fc

Please sign in to comment.