Skip to content

Commit

Permalink
Finish Doxygen
Browse files Browse the repository at this point in the history
  • Loading branch information
BowDown097 committed Oct 13, 2023
1 parent 6421ab3 commit 1d500b8
Show file tree
Hide file tree
Showing 14 changed files with 175 additions and 92 deletions.
56 changes: 56 additions & 0 deletions DOXYGEN_MAINPAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
A Qt library to interface with YouTube's InnerTube API.

## Getting Started
To get started, use @ref InnerTube::createContext. This example code will create an ideal, working context for you:
```cpp
InnerTube::instance().createContext(InnertubeClient("WEB", "2.20230718.01.00", "DESKTOP"));
```
Here, a context is created around a client of the WEB type, version 2.20230718.01.00, on the DESKTOP platform.<br><br>
To make a request, use @ref InnerTube::get. This example code provides a good way to test if things are working:
```cpp
InnertubeReply* reply = InnerTube::instance().get<InnertubeEndpoints::Next>("dQw4w9WgXcQ");
connect(reply, qOverload<const InnertubeEndpoints::Next&>(&InnertubeReply::finished), this, [](const auto& next) {
qDebug() << next.response.primaryInfo.title.text;
});
```
Here, a request is made to the @ref InnertubeEndpoints::Next "Next endpoint" supplied with the video ID for [the classic Rick Roll video](https://www.youtube.com/watch?v=dQw4w9WgXcQ).
Once the request finishes, the response is captured and the video title is printed (which should be "Rick Astley - Never Gonna Give You Up (Official Music Video)").

## Authenticating (easily)
If you wish to support logging in, you can use the authentication methods in the @ref InnerTube class.

To get started, use @ref InnerTube::authenticate. This will open up a browser window on YouTube's login page that captures the necessary credentials. Once all of the necessary credentials have been captured, the signal @ref InnertubeAuthStore::authenticateSuccess is emitted, and you can do whatever you want beyond that point. The method @ref InnerTube::authStore returns the @ref InnertubeAuthStore in question.<br>
**Note that this method depends on Qt Web Engine. See the [Authenticating Manually](@ref AuthenticatingManually) section if you wish to not use Qt Web Engine.**

If you want to dive deeper, have a look at the @ref InnertubeAuthStore class.

## Authenticating (manually) {#AuthenticatingManually}
**Be warned, this not an easy process. Also, this guide will be for Firefox, but there will likely be a lot of overlap with Chromium-based browsers anyway.**

### Getting the Credentials
1. Go to the YouTube home page.
2. Open up Developer Tools (pressing Ctrl+Shift+I or F12 works, or you can use the browser menu) and go to the Network tab.
3. In the "Filter URLs" bar, type "youtubei/v1" and select any result **except for ones which contain "log_event".**
4. In the "Headers" section, in the "Filter Headers" bar, type "cookie" and look for the entry that's just called "Cookie". Right click the entry and click "Copy Value".
5. Inside of what you just copied, look for the values for APISID, HSID, SAPISID, SID, SSID, and VISITOR\_INFO1\_LIVE. For example, in VISITOR\_INFO1\_LIVE=xxx, the value would be xxx.

### Putting It Into the Library
To feed this data into the library, create a QJsonObject like so:
```cpp
QJsonObject obj {
{ "apisid", "<YOUR APISID HERE>" },
{ "hsid", "<YOUR HSID HERE>" },
{ "sapisid", "<YOUR SAPISID HERE>" },
{ "sid", "<YOUR SID HERE>" },
{ "ssid", "<YOUR SSID HERE>" },
{ "visitorInfo", "<YOUR VISITOR_INFO1_LIVE HERE>" }
};
```
Then, see steps 2 and 3 in [Restoring Authentication Data](@ref RestoringAuthenticationData).

## Restoring Authentication Data {#RestoringAuthenticationData}
Some facilities have been provided for making this easier. Here's the way I recommend doing it:
1. Once the user has authenticated successfully, get the credentials as a JSON object using @ref InnertubeAuthStore::toJson and save it to a JSON file on the system.
2. To restore the data, read the JSON file back to a QJsonObject, then provide this QJsonObject to @ref InnerTube::authenticateFromJson.
3. To check if this was successful, use @ref InnerTube::hasAuthenticated.
6 changes: 3 additions & 3 deletions Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ MARKDOWN_ID_STYLE = DOXYGEN
# globally by setting AUTOLINK_SUPPORT to NO.
# The default value is: YES.

AUTOLINK_SUPPORT = YES
AUTOLINK_SUPPORT = NO

# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
# to include (a tag file for) the STL sources as input, then you should set this
Expand Down Expand Up @@ -943,7 +943,7 @@ WARN_LOGFILE =
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.

INPUT = src
INPUT = DOXYGEN_MAINPAGE.md src

# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
Expand Down Expand Up @@ -1158,7 +1158,7 @@ FILTER_SOURCE_PATTERNS =
# (index.html). This can be useful if you have a project on for instance GitHub
# and want to reuse the introduction page also for the doxygen output.

USE_MDFILE_AS_MAINPAGE =
USE_MDFILE_AS_MAINPAGE = DOXYGEN_MAINPAGE.md

# The Fortran standard specifies that for fixed formatted Fortran code all
# characters from position 72 are to be considered as comment. A common
Expand Down
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
# innertube-qt
A Qt library to interface with YouTube's InnerTube API.

## Building without WebEngine
If you are not going to use the authentication features of this library, or just really want to disable WebEngine and do authentication manually, you can turn the ``INNERTUBE_NO_WEBENGINE`` option in the CMakeLists.txt file on.
42 changes: 20 additions & 22 deletions src/innertube.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,37 +25,35 @@ concept EndpointWithData = std::derived_from<T, InnertubeEndpoints::BaseEndpoint
* and @ref subscribe methods) are built to work with a recent version of the WEB client.
* If not operating under these conditions, it is likely that they will be mostly or completely useless,
* in which case you will have to use @ref InnertubeEndpoints::BaseEndpoint::get and work with the raw responses yourself.
* But feel free to try them still if you wish.<br><br>
* To get started, use @ref createContext. This example code will create an ideal, working context for you:
* @code{.cpp}
* InnerTube::instance().createContext(InnertubeClient("WEB", "2.20230718.01.00", "DESKTOP"));
* @endcode
* Here, a context is created around a client of the WEB type, version 2.20230718.01.00, on the DESKTOP platform.<br><br>
* To make a request, use @ref get. This example code provides a good way to test if things are working:
* @code{.cpp}
* InnertubeReply* reply = InnerTube::instance().get<InnertubeEndpoints::Next>("dQw4w9WgXcQ");
* connect(reply, qOverload<const InnertubeEndpoints::Next&>(&InnertubeReply::finished), this, [](const auto& next) {
* qDebug() << next.response.primaryInfo.title.text;
* });
* @endcode
* Here, a request is made to the @ref InnertubeEndpoints::Next "Next endpoint" supplied with the video ID
* for [the classic Rick Roll video](https://www.youtube.com/watch?v=dQw4w9WgXcQ).
* Once the request finishes, the response is captured and the video title is printed
* (which should be "Rick Astley - Never Gonna Give You Up (Official Music Video)").<br><br>
* If you wish to support logging in, use @ref authenticate.
* This will open up a browser window on YouTube's login page that captures the necessary credentials
* for requests to the API to be authenticated and saves them in the
* @ref instance "global instance"'s @ref InnertubeAuthStore "authentication storage".
* But feel free to try them still if you wish.
*/
class InnerTube
{
public:
static InnerTube& instance() { static InnerTube it; return it; }
InnertubeAuthStore* authStore() const { return m_authStore; }
InnertubeContext* context() const { return m_context; }
bool hasAuthenticated() const { return m_authStore->populated; }

/**
* @brief Shorthand for @ref InnertubeAuthStore::populated.
*/
bool hasAuthenticated() const { return m_authStore->populated(); }

#ifndef INNERTUBE_NO_WEBENGINE
/**
* @brief Shorthand for @ref InnertubeAuthStore::authenticate.
*/
void authenticate() { m_authStore->authenticate(m_context); }
#endif

/**
* @brief Shorthand for @ref InnertubeAuthStore::authenticateFromJson.
*/
void authenticateFromJson(const QJsonObject& obj) { m_authStore->authenticateFromJson(obj, m_context); }

/**
* @brief Shorthand for @ref InnertubeAuthStore::unauthenticate.
*/
void unauthenticate() { m_authStore->unauthenticate(m_context); }

void createContext(const InnertubeClient& client, const InnertubeClickTracking& clickTracking = InnertubeClickTracking(),
Expand Down
4 changes: 2 additions & 2 deletions src/innertube/endpoints/base/baseendpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ namespace InnertubeEndpoints
{
QMap<QString, QString> headers;

if (authStore->populated)
if (authStore->populated())
{
headers.insert({
{ "Authorization", authStore->generateSAPISIDHash() },
{ "Cookie", authStore->getNecessaryLoginCookies() },
{ "Cookie", authStore->toCookieString() },
{ "X-Goog-AuthUser", "0" }
});
}
Expand Down
63 changes: 27 additions & 36 deletions src/innertube/itc-objects/innertubeauthstore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
#include <QWebEngineCookieStore>
#include <QWebEngineProfile>
#include <QWebEngineView>
#endif

void InnertubeAuthStore::authenticate(InnertubeContext*& context)
{
#ifndef INNERTUBE_NO_WEBENGINE
QWidget* authWindow = new QWidget;
authWindow->setFixedSize(authWindow->size());
authWindow->setWindowTitle("YouTube Login");
Expand All @@ -26,15 +24,36 @@ void InnertubeAuthStore::authenticate(InnertubeContext*& context)
view->show();

connect(QWebEngineProfile::defaultProfile()->cookieStore(), &QWebEngineCookieStore::cookieAdded, this, &InnertubeAuthStore::cookieAdded);
connect(this, &InnertubeAuthStore::authenticateSuccess, this, [this, authWindow, context] {
authWindow->deleteLater();
context->client.visitorData = SimpleProtobuf::padded(visitorInfo);
});
}

QEventLoop loop;
connect(this, &InnertubeAuthStore::gotSIDs, &loop, &QEventLoop::quit);
loop.exec();
void InnertubeAuthStore::cookieAdded(const QNetworkCookie& cookie)
{
if (populated())
return;

authWindow->deleteLater();
context->client.visitorData = SimpleProtobuf::padded(visitorInfo);
#endif
qDebug().noquote().nospace() << "New cookie: " << cookie.name() << "=" << cookie.value();

if (cookie.name() == "APISID")
apisid = cookie.value();
else if (cookie.name() == "HSID")
hsid = cookie.value();
else if (cookie.name() == "SAPISID")
sapisid = cookie.value();
else if (cookie.name() == "SID")
sid = cookie.value();
else if (cookie.name() == "SSID")
ssid = cookie.value();
else if (cookie.name() == "VISITOR_INFO1_LIVE")
visitorInfo = cookie.value();

if (populated())
emit authenticateSuccess();
}
#endif

void InnertubeAuthStore::authenticateFromJson(const QJsonObject& obj, InnertubeContext*& context)
{
Expand All @@ -45,7 +64,6 @@ void InnertubeAuthStore::authenticateFromJson(const QJsonObject& obj, InnertubeC
ssid = obj["ssid"].toString();
visitorInfo = obj["visitorInfo"].toString();
context->client.visitorData = SimpleProtobuf::padded(visitorInfo);
populated = !apisid.isEmpty() && !hsid.isEmpty() && !sapisid.isEmpty() && !sid.isEmpty() && !ssid.isEmpty() && !visitorInfo.isEmpty();
}

QString InnertubeAuthStore::generateSAPISIDHash()
Expand All @@ -71,36 +89,9 @@ void InnertubeAuthStore::unauthenticate(InnertubeContext*& context)
{
apisid.clear();
hsid.clear();
populated = false;
sapisid.clear();
sid.clear();
ssid.clear();
visitorInfo.clear();
context->client.visitorData.clear();
}

#ifndef INNERTUBE_NO_WEBENGINE
void InnertubeAuthStore::cookieAdded(const QNetworkCookie& cookie)
{
qDebug().noquote().nospace() << "New cookie: " << cookie.name() << "=" << cookie.value();

if (cookie.name() == "APISID")
apisid = cookie.value();
else if (cookie.name() == "HSID")
hsid = cookie.value();
else if (cookie.name() == "SAPISID")
sapisid = cookie.value();
else if (cookie.name() == "SID")
sid = cookie.value();
else if (cookie.name() == "SSID")
ssid = cookie.value();
else if (cookie.name() == "VISITOR_INFO1_LIVE")
visitorInfo = cookie.value();

if (!apisid.isEmpty() && !hsid.isEmpty() && !sapisid.isEmpty() && !sid.isEmpty() && !ssid.isEmpty() && !visitorInfo.isEmpty())
{
populated = true;
emit gotSIDs();
}
}
#endif
46 changes: 42 additions & 4 deletions src/innertube/itc-objects/innertubeauthstore.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,65 @@
#include <QNetworkCookie>
#endif

/**
* @brief Stores YouTube authentication credentials.
*/
class InnertubeAuthStore : public QObject
{
Q_OBJECT
public:
QString apisid;
QString hsid;
bool populated = false;
QString sapisid;
QString sid;
QString ssid;
QString visitorInfo;

explicit InnertubeAuthStore(QObject* parent = nullptr) : QObject(parent) {}

#ifndef INNERTUBE_NO_WEBENGINE
/**
* @brief Opens up a browser window on YouTube's login page that captures the necessary credentials.
* @param context @ref InnertubeContext to write visitor data to (encoded version of @ref visitorInfo).
*/
void authenticate(InnertubeContext*& context);
#endif

/**
* @brief Store authentication credentials from JSON data returned by @ref toJson.
* @param context @ref InnertubeContext to write visitor data to (encoded version of @ref visitorInfo).
*/
void authenticateFromJson(const QJsonObject& obj, InnertubeContext*& context);

/**
* @brief Clear all authentication credentials.
* @param context @ref InnertubeContext to clear visitor data from (encoded version of @ref visitorInfo).
*/
void unauthenticate(InnertubeContext*& context);

/**
* @return If all credentials have been captured (all class members have a value).
*/
bool populated() const
{ return !(apisid.isEmpty() || hsid.isEmpty() || sapisid.isEmpty() || sid.isEmpty() || ssid.isEmpty() || visitorInfo.isEmpty()); }

/**
* @return The authentication credentials as a string to be passed as the Cookie header in a web request.
*/
QString toCookieString() const
{ return QStringLiteral("SID=%1; HSID=%2; SSID=%3; SAPISID=%4; APISID=%5").arg(sid, hsid, ssid, sapisid, apisid); }

/**
* @return A hashed version of the @ref sapisid "SAPISID" to be passed as the Authentication header in a web request.
*/
QString generateSAPISIDHash();
QString getNecessaryLoginCookies() { return QStringLiteral("SID=%1; HSID=%2; SSID=%3; SAPISID=%4; APISID=%5").arg(sid, hsid, ssid, sapisid, apisid); }

/**
* @return The authentication credentials as a JSON object to be used with @ref authenticateFromJson.
*/
QJsonObject toJson() const;
void unauthenticate(InnertubeContext*& context);
signals:
void gotSIDs();
void authenticateSuccess();
#ifndef INNERTUBE_NO_WEBENGINE
private slots:
void cookieAdded(const QNetworkCookie& cookie);
Expand Down
2 changes: 1 addition & 1 deletion src/innertube/itc-objects/innertubeclicktracking.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class InnertubeClickTracking
{
public:
QString clickTrackingParams;
explicit InnertubeClickTracking(const QString& ctp = "") : clickTrackingParams(ctp) {}
explicit InnertubeClickTracking(const QString& clickTrackingParams = "") : clickTrackingParams(clickTrackingParams) {}
QJsonObject toJson() const { return {{ "clickTrackingParams", clickTrackingParams }}; }
};

Expand Down
13 changes: 7 additions & 6 deletions src/innertube/itc-objects/innertubeclient.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#include "innertubeclient.h"
#include "sslhttprequest.h"

InnertubeClient::InnertubeClient(const QString& clientName, const QString& clientVersion, const QString& platform, const QString& userAgent,
const QString& browserName, const QString& browserVersion, const QString& userInterfaceTheme,
const QString& clientFormFactor, const InnertubeConfigInfo& configInfo, const QString& deviceMake,
const QString& deviceModel, const QString& gl, const QString& hl, const QString& originalUrl, const QString& osName,
const QString& osVersion, const QString& remoteHost, int screenDensityFloat, int screenPixelDensity,
const QString& timeZone)
InnertubeClient::InnertubeClient(const QString& clientName, const QString& clientVersion, const QString& platform,
const QString& userAgent, const QString& browserName, const QString& browserVersion,
const QString& userInterfaceTheme, const QString& clientFormFactor,
const InnertubeConfigInfo& configInfo, const QString& deviceMake, const QString& deviceModel,
const QString& gl, const QString& hl, const QString& originalUrl, const QString& osName,
const QString& osVersion, const QString& remoteHost, int screenDensityFloat,
int screenPixelDensity, const QString& timeZone)
: browserName(browserName),
browserVersion(browserVersion),
clientFormFactor(clientFormFactor),
Expand Down
19 changes: 10 additions & 9 deletions src/innertube/itc-objects/innertubeclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,22 @@ class InnertubeClient
QString osVersion;
QString platform;
QString remoteHost;
int screenDensityFloat = 0;
int screenPixelDensity = 0;
int screenDensityFloat;
int screenPixelDensity;
QString timeZone;
QString userAgent;
QString userInterfaceTheme;
QString visitorData;

InnertubeClient() = default;
InnertubeClient(const QString& clientName, const QString& clientVersion, const QString& platform, const QString& userAgent = "",
const QString& browserName = "Firefox", const QString& browserVersion = "103.0",
const QString& userInterfaceTheme = "USER_INTERFACE_THEME_DARK", const QString& clientFormFactor = "UNKNOWN_FORM_FACTOR",
const InnertubeConfigInfo& configInfo = InnertubeConfigInfo(), const QString& deviceMake = "", const QString& deviceModel = "",
const QString& gl = "US", const QString& hl = "en", const QString& originalUrl = "", const QString& osName = "",
const QString& osVersion = "", const QString& remoteHost = "", int screenDensityFloat = 2, int screenPixelDensity = 2,
const QString& timeZone = "");
InnertubeClient(const QString& clientName, const QString& clientVersion, const QString& platform,
const QString& userAgent = "", const QString& browserName = "Firefox",
const QString& browserVersion = "103.0", const QString& userInterfaceTheme = "USER_INTERFACE_THEME_DARK",
const QString& clientFormFactor = "UNKNOWN_FORM_FACTOR", const InnertubeConfigInfo& configInfo = InnertubeConfigInfo(),
const QString& deviceMake = "", const QString& deviceModel = "", const QString& gl = "US",
const QString& hl = "en", const QString& originalUrl = "", const QString& osName = "",
const QString& osVersion = "", const QString& remoteHost = "", int screenDensityFloat = 2,
int screenPixelDensity = 2, const QString& timeZone = "");
QJsonObject toJson() const;
};

Expand Down
Loading

0 comments on commit 1d500b8

Please sign in to comment.