Skip to content

Commit

Permalink
Big big changes
Browse files Browse the repository at this point in the history
This commit was supposed to be smaller but I just kept going lol.
This is everything that's happened:
- getRaw method in InnerTube class for non-WEB clients
- Use parameter pack for get requests instead of weird explicit
  parameters
- Reorganize parameters of some endpoints for parameter pack
- Accept endpoint in BaseEndpoint as template class parameter instead of
  parameter in get() method, basically needed for getRaw
- Split like and subscribe endpoints, required by this change
- Move GetNotificationMenu to notification endpoints folder where it
  should be
  • Loading branch information
BowDown097 committed Nov 12, 2023
1 parent 4cc45b1 commit 59919cc
Show file tree
Hide file tree
Showing 49 changed files with 314 additions and 200 deletions.
10 changes: 7 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,27 @@ add_library(innertube-qt
src/innertube/innertubeexception.h
src/innertube/innertubereply.h
src/innertube/endpoints/base/basebrowseendpoint.cpp src/innertube/endpoints/base/basebrowseendpoint.h
src/innertube/endpoints/base/baseendpoint.cpp src/innertube/endpoints/base/baseendpoint.h
src/innertube/endpoints/base/baseendpoint.h
src/innertube/endpoints/browse/browsechannel.cpp src/innertube/endpoints/browse/browsechannel.h
src/innertube/endpoints/browse/browsehistory.cpp src/innertube/endpoints/browse/browsehistory.h
src/innertube/endpoints/browse/browsehome.cpp src/innertube/endpoints/browse/browsehome.h
src/innertube/endpoints/browse/browsesubscriptions.cpp src/innertube/endpoints/browse/browsesubscriptions.h
src/innertube/endpoints/browse/browsetrending.cpp src/innertube/endpoints/browse/browsetrending.h
src/innertube/endpoints/innertubeendpoints.h
src/innertube/endpoints/like/like.cpp src/innertube/endpoints/like/like.h
src/innertube/endpoints/like/baselikeendpoint.h
src/innertube/endpoints/like/dislike.h
src/innertube/endpoints/like/like.h
src/innertube/endpoints/like/removelike.h
src/innertube/endpoints/live_chat/getlivechat.cpp src/innertube/endpoints/live_chat/getlivechat.h
src/innertube/endpoints/live_chat/getlivechatreplay.cpp src/innertube/endpoints/live_chat/getlivechatreplay.h
src/innertube/endpoints/live_chat/sendmessage.cpp src/innertube/endpoints/live_chat/sendmessage.h
src/innertube/endpoints/misc/accountmenu.cpp src/innertube/endpoints/misc/accountmenu.h
src/innertube/endpoints/misc/getnotificationmenu.cpp src/innertube/endpoints/misc/getnotificationmenu.h
src/innertube/endpoints/misc/search.cpp src/innertube/endpoints/misc/search.h
src/innertube/endpoints/misc/unseencount.cpp src/innertube/endpoints/misc/unseencount.h
src/innertube/endpoints/notification/getnotificationmenu.cpp src/innertube/endpoints/notification/getnotificationmenu.h
src/innertube/endpoints/notification/modifychannelpreference.cpp src/innertube/endpoints/notification/modifychannelpreference.h
src/innertube/endpoints/subscription/subscribe.cpp src/innertube/endpoints/subscription/subscribe.h
src/innertube/endpoints/subscription/unsubscribe.cpp src/innertube/endpoints/subscription/unsubscribe.h
src/innertube/endpoints/video/next.cpp src/innertube/endpoints/video/next.h
src/innertube/endpoints/video/player.cpp src/innertube/endpoints/video/player.h
src/innertube/endpoints/video/updatedmetadata.cpp src/innertube/endpoints/video/updatedmetadata.h
Expand Down
22 changes: 20 additions & 2 deletions DOXYGEN_MAINPAGE.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
A Qt library to interface with YouTube's InnerTube API.

## Getting Started
## Getting Started {#GettingStarted}
To get started, use @ref InnerTube::createContext. This example code will create an ideal, working context for you:
```cpp
InnerTube::instance().createContext(InnertubeClient(InnertubeClient::ClientType::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>
Here, a context is created around a client of the WEB type, version 2.20230718.01.00, on the DESKTOP platform. If you're not using the WEB client, you probably want to take a look at the [Raw Requests](@ref RawRequests) section.<br><br>
To make a request, use @ref InnerTube::get. This example code provides a good way to test if things are working:
```cpp
Expand All @@ -17,6 +17,24 @@ connect(reply, qOverload<const InnertubeEndpoints::Next&>(&InnertubeReply::finis
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)").

## Raw Requests {#RawRequests}
The WEB client is the only client officially supported by this library. If you are using other clients, raw requests will likely be the only way to get data without any errors, with the exception of the @ref InnertubeEndpoints::Player "Player", @ref InnertubeEndpoints::SendMessage "SendMessage", @ref InnertubeEndpoints::Subscribe "Subscribe", and @ref InnertubeEndpoints::UnseenCount "UnseenCount" endpoints on most clients (and maybe more from client to client).

To make a **raw** request, use @ref InnerTube::getRaw. Like before, here's example code for getting the Rick Roll video's data, with client setup too as a bonus. We will be using the obscure ANDROID\_TESTSUITE client for this example.
```cpp
InnerTube::instance().createContext(InnertubeClient(InnertubeClient::ClientType::ANDROID_TESTSUITE, "1.9", "MOBILE"));

InnertubeReply* reply = InnerTube::instance().getRaw<InnertubeEndpoints::Next>({
{ "playbackContext", InnertubePlaybackContext(false, "").toJson() },
{ "videoId", "dQw4w9WgXcQ" }
});

connect(reply, &InnertubeReply::finishedRaw, this, [](const QJsonValue& v) {
qDebug() << InnertubeObjects::InnertubeString(v["contents"]["singleColumnWatchNextResults"]["results"]["results"]["contents"][0]["itemSectionRenderer"]["contents"][0]["videoMetadataRenderer"]["title"]).text;
});
```
This does the same thing as the code in the [Getting Started](@ref GettingStarted) section.
## Authenticating (easily)
If you wish to support logging in, you can use the authentication methods in the @ref InnerTube class.
Expand Down
30 changes: 12 additions & 18 deletions src/innertube.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,12 @@
void InnerTube::like(const QJsonValue& endpoint, bool liking)
{
QThreadPool::globalInstance()->start([this, endpoint, liking] {
if (endpoint["removeLikeParams"].isObject())
{
InnertubeEndpoints::Like(
endpoint["target"]["videoId"].toString(),
endpoint["removeLikeParams"].toString(),
liking, true, m_context, m_authStore
);
}
else
{
InnertubeEndpoints::Like(
endpoint["target"]["videoId"].toString(),
liking ? endpoint["likeParams"].toString() : endpoint["dislikeParams"].toString(),
liking, false, m_context, m_authStore
);
}
if (liking)
InnertubeEndpoints::Like(m_context, m_authStore, endpoint["target"]["videoId"].toString(), endpoint["likeParams"].toString());
else if (!liking)
InnertubeEndpoints::Dislike(m_context, m_authStore, endpoint["target"]["videoId"].toString(), endpoint["likeParams"].toString());
else if (endpoint["removeLikeParams"].isObject())
InnertubeEndpoints::RemoveLike(m_context, m_authStore, endpoint["target"]["videoId"].toString(), endpoint["removeLikeParams"].toString());
});
}

Expand All @@ -41,11 +31,15 @@ void InnerTube::subscribe(const QStringList& channelIds, bool subscribing)

void InnerTube::subscribeBlocking(const QJsonValue& endpoint, bool subscribing)
{
QList<QString> channelIds;
QStringList channelIds;
const QJsonArray channelIdsJson = endpoint["channelIds"].toArray();
for (const QJsonValue& v : channelIdsJson)
channelIds.append(v.toString());
InnertubeEndpoints::Subscribe(channelIds, endpoint["params"].toString(), subscribing, m_context, m_authStore);

if (subscribing)
InnertubeEndpoints::Subscribe(m_context, m_authStore, channelIds, endpoint["params"].toString());
else
InnertubeEndpoints::Unsubscribe(m_context, m_authStore, channelIds, endpoint["params"].toString());
}

void InnerTube::subscribeBlocking(const QStringList& channelIds, bool subscribing)
Expand Down
78 changes: 46 additions & 32 deletions src/innertube.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,25 @@
#include "innertube/innertubeexception.h"
#include "innertube/innertubereply.h"
#include "innertube/itc-objects/innertubeauthstore.h"
#include <QJsonDocument>
#include <QThreadPool>
#include <type_traits>

template<class T, class... U>
static constexpr bool innertube_is_any_v = std::disjunction_v<std::is_same<T, U>...>;

/**
* @brief An @ref InnertubeEndpoints::BaseEndpoint "Innertube endpoint" that returns data.
* @details The @ref InnertubeEndpoints::Like "Like", @ref InnertubeEndpoints::SendMessage "SendMessage",
* and @ref InnertubeEndpoints::Subscribe "Subscribe" endpoints cannot be used here.<br>
* Use their respective methods in the InnerTube class as opposed to the
* @ref InnerTube::get "get" and @ref InnerTube::getBlocking "getBlocking" methods.
*/
template<typename T>
concept EndpointWithData = std::derived_from<T, InnertubeEndpoints::BaseEndpoint> &&
!std::same_as<T, InnertubeEndpoints::Like> &&
!std::same_as<T, InnertubeEndpoints::SendMessage> &&
!std::same_as<T, InnertubeEndpoints::Subscribe>;
template<class C>
concept EndpointWithData = requires(C c)
{
[]<InnertubeEndpoints::CompTimeStr X>(InnertubeEndpoints::BaseEndpoint<X>&){}(c);
} && !innertube_is_any_v<C, InnertubeEndpoints::Like, InnertubeEndpoints::SendMessage, InnertubeEndpoints::Subscribe>;

/**
* @brief The main attraction. Pretty much all of the interfacing with the library happens here.
Expand Down Expand Up @@ -63,22 +67,19 @@ class InnerTube

/**
* @brief Get the result of an Innertube request asynchronously.
* @param data Input data, for example a channel ID or search query.
* Refer to the first parameter of the endpoint's constructor.
* @param continuationToken Continuation token provided by a previous Innertube response.
* @param params Not required for any request, but used for things like search filtering.
* Refer to the documentation for the constructor of the endpoint objects that have this as an argument.
* @param args Arguments to pass in. See individual endpoint constructor parameters, excluding the
* @ref InnertubeContext and @ref InnertubeAuthStore.
* @return @ref InnertubeReply "An object" that emits signals containing the result.
* Don't worry, it's freed when the request finishes - you don't have to manually delete it.
*/
template<EndpointWithData E>
InnertubeReply* get(const QString& data = "", const QString& continuationToken = "", const QString& params = "")
InnertubeReply* get(auto&&... args)
{
InnertubeReply* reply = new InnertubeReply;
QThreadPool::globalInstance()->start([this, reply, data, continuationToken, params] {
QThreadPool::globalInstance()->start([this, reply, ...args = std::forward<decltype(args)>(args)] {
try
{
E endpoint = getBlocking<E>(data, continuationToken, params);
E endpoint = getBlocking<E>(std::forward<decltype(args)>(args)...);
emit reply->finished(endpoint);
}
catch (const InnertubeException& ie)
Expand All @@ -93,25 +94,41 @@ class InnerTube

/**
* @brief Get the result of an Innertube request synchronously.
* @details See @ref get for parameter details.
* @details See @ref get for more details.
*/
template<EndpointWithData E>
E getBlocking(auto&&... args) { return E(m_context, m_authStore, std::forward<decltype(args)>(args)...); }

/**
* @brief Get the result of an Innertube request asynchronously.
* @details Use this if you are not using the WEB client.
* @param body JSON body for the request. See the endpoint's constructor code for what to do.
* @return @ref InnertubeReply "An object" that emits signals containing the result.
* Specifically, you'll want to connect to finishedRaw.
* Don't worry, it's freed when the request finishes - you don't have to manually delete it.
*/
template<EndpointWithData E>
InnertubeReply* getRaw(const QJsonObject& body)
{
InnertubeReply* reply = new InnertubeReply;
QThreadPool::globalInstance()->start([this, reply, body] {
QJsonValue v = getRawBlocking<E>(body);
emit reply->finishedRaw(v);
reply->deleteLater();
});
return reply;
}

/**
* @brief Get the raw result of an Innertube request synchronously.
* @details See @ref getRaw for more details.
*/
template<EndpointWithData E>
E getBlocking(const QString& data = "", const QString& continuationToken = "", const QString& params = "")
QJsonValue getRawBlocking(QJsonObject body)
{
if constexpr (is_any_v<E, InnertubeEndpoints::GetLiveChat, InnertubeEndpoints::ModifyChannelPreference,
InnertubeEndpoints::Player, InnertubeEndpoints::UpdatedMetadata>)
return E(data, m_context, m_authStore);
else if constexpr (is_any_v<E, InnertubeEndpoints::BrowseHistory, InnertubeEndpoints::GetLiveChatReplay,
InnertubeEndpoints::GetNotificationMenu, InnertubeEndpoints::Next>)
return E(data, m_context, m_authStore, continuationToken);
else if constexpr (is_any_v<E, InnertubeEndpoints::BrowseChannel, InnertubeEndpoints::Search,
InnertubeEndpoints::SendMessage>)
return E(data, m_context, m_authStore, continuationToken, params);
else if constexpr (is_any_v<E, InnertubeEndpoints::AccountMenu, InnertubeEndpoints::BrowseTrending,
InnertubeEndpoints::UnseenCount>)
return E(m_context, m_authStore);
else
return E(m_context, m_authStore, continuationToken);
body.insert("context", m_context->toJson());
QByteArray data = E::get(m_context, m_authStore, body);
return QJsonDocument::fromJson(data).object();
}

void like(const QJsonValue& endpoint, bool liking);
Expand All @@ -124,9 +141,6 @@ class InnerTube
private:
InnertubeAuthStore* m_authStore = new InnertubeAuthStore;
InnertubeContext* m_context = new InnertubeContext;

template<class T, class... U>
static constexpr bool is_any_v = std::disjunction_v<std::is_same<T, U>...>;
};

#endif // INNERTUBE_H
2 changes: 1 addition & 1 deletion src/innertube/endpoints/base/basebrowseendpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace InnertubeEndpoints
if (!query.isEmpty())
body.insert("query", query);

data = get("browse", context, authStore, body);
data = get(context, authStore, body);
}

QJsonValue BaseBrowseEndpoint::getTabRenderer(const QString& name) const
Expand Down
2 changes: 1 addition & 1 deletion src/innertube/endpoints/base/basebrowseendpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace InnertubeEndpoints
* @brief The parent of all browse endpoint objects.
* @details This doesn't really serve any purpose for the user, just a nice abstraction internally.
*/
class BaseBrowseEndpoint : public BaseEndpoint
class BaseBrowseEndpoint : public BaseEndpoint<"browse">
{
protected:
QByteArray data;
Expand Down
49 changes: 0 additions & 49 deletions src/innertube/endpoints/base/baseendpoint.cpp

This file was deleted.

53 changes: 50 additions & 3 deletions src/innertube/endpoints/base/baseendpoint.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@
#define BASEENDPOINT_H
#include "innertube/itc-objects/innertubeauthstore.h"
#include "sslhttprequest.h"
#include <QEventLoop>

class InnerTube;
namespace InnertubeEndpoints
{
template<size_t N>
struct CompTimeStr
{
char data[N] {};
consteval CompTimeStr(const char (&str)[N]) { std::copy_n(str, N, data); }
};

/**
* @brief The parent of all endpoint objects.
*/
template<CompTimeStr endpoint>
class BaseEndpoint
{
public:
Expand All @@ -19,10 +28,48 @@ namespace InnertubeEndpoints
* @param body The JSON body of the request.
* @return The request's response body.
*/
QByteArray get(const QString& endpoint, InnertubeContext* context, InnertubeAuthStore* authStore, const QJsonObject& body);
static QByteArray get(InnertubeContext* context, InnertubeAuthStore* authStore, const QJsonObject& body)
{
return getData(QStringLiteral("https://www.youtube.com/youtubei/v1/%1?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8&prettyPrint=false").arg(endpoint.data),
getNeededHeaders(context, authStore), body);
}
private:
QByteArray getData(const QString& path, const QVariantMap& headers, const QJsonObject& body);
QVariantMap getNeededHeaders(InnertubeContext* context, InnertubeAuthStore* authStore);
static QByteArray getData(const QString& path, const QVariantMap& headers, const QJsonObject& body)
{
QScopedPointer<SslHttpRequest, QScopedPointerDeleteLater> req(new SslHttpRequest(path, SslHttpRequest::RequestMethod::Post));
req->setBody(body);
req->setHeaders(headers);
req->send();

QEventLoop loop;
QObject::connect(req.data(), &SslHttpRequest::finished, &loop, &QEventLoop::quit);
loop.exec();

return req->payload();
}

static QVariantMap getNeededHeaders(InnertubeContext* context, InnertubeAuthStore* authStore)
{
QVariantMap headers;

if (authStore->populated())
{
headers.insert({
{ "Authorization", authStore->generateSAPISIDHash() },
{ "Cookie", authStore->toCookieString() },
{ "X-Goog-AuthUser", "0" }
});
}

headers.insert({
{ "X-Goog-Visitor-Id", context->client.visitorData },
{ "X-YOUTUBE-CLIENT-NAME", static_cast<int>(context->client.clientType) },
{ "X-YOUTUBE-CLIENT-VERSION", context->client.clientVersion },
{ "X-ORIGIN", "https://www.youtube.com" }
});

return headers;
}
};
}

Expand Down
Loading

0 comments on commit 59919cc

Please sign in to comment.