2022-12-22 05:20:32 +01:00
|
|
|
#include <iostream>
|
|
|
|
#include <fstream>
|
|
|
|
#include <set>
|
2022-12-22 06:18:22 +01:00
|
|
|
#include <sys/stat.h>
|
2022-12-22 05:20:32 +01:00
|
|
|
#include <curl/curl.h>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
using namespace std;
|
|
|
|
using json = nlohmann::json;
|
|
|
|
|
|
|
|
vector<string> getFileContent(string filePath);
|
|
|
|
json getJson(string url);
|
2022-12-22 06:18:22 +01:00
|
|
|
void createDirectory(string path),
|
|
|
|
print(ostringstream* toPrint),
|
2022-12-22 05:20:32 +01:00
|
|
|
treatComment(json comment);
|
|
|
|
string getHttps(string url);
|
|
|
|
size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp);
|
2022-12-22 06:18:22 +01:00
|
|
|
bool doesFileExist(string filePath),
|
|
|
|
writeFile(string filePath, string option, string toWrite);
|
2022-12-22 05:20:32 +01:00
|
|
|
|
|
|
|
#define API_KEY "AIzaSy..."
|
|
|
|
|
|
|
|
// Note that this printing approach is only safe in a mono-thread context.
|
|
|
|
#define PRINT(x) toPrint << x; print(&toPrint);
|
|
|
|
ostringstream toPrint;
|
|
|
|
|
2022-12-22 06:18:22 +01:00
|
|
|
set<string> channelsAlreadyTreated,
|
|
|
|
channelsToTreat;
|
2022-12-22 05:20:32 +01:00
|
|
|
unsigned int commentsCount = 0;
|
|
|
|
|
|
|
|
int main()
|
|
|
|
{
|
2022-12-22 06:18:22 +01:00
|
|
|
string channelsToTreatFilePath = "channelsToTreat.txt";
|
|
|
|
vector<string> channelsToTreatVec = getFileContent(channelsToTreatFilePath);
|
|
|
|
channelsToTreat = set(channelsToTreatVec.begin(), channelsToTreatVec.end());
|
|
|
|
|
|
|
|
string channelsDirectory = "channels/";
|
|
|
|
createDirectory(channelsDirectory);
|
|
|
|
|
|
|
|
for(const auto& entry : filesystem::directory_iterator(channelsDirectory))
|
|
|
|
{
|
|
|
|
channelsAlreadyTreated.insert(entry.path().filename());
|
|
|
|
}
|
|
|
|
|
|
|
|
PRINT(channelsToTreat.size() << " channel(s) to treat")
|
|
|
|
PRINT(channelsAlreadyTreated.size() << " channel(s) already treated")
|
|
|
|
|
|
|
|
while(!channelsToTreat.empty())
|
|
|
|
{
|
|
|
|
string channelToTreat = *channelsToTreat.begin();
|
|
|
|
PRINT("Treating channel " << channelToTreat << " (treated: " << channelsAlreadyTreated.size() << ", to treat: " << channelsToTreat.size() << ")")
|
|
|
|
|
|
|
|
string pageToken = "";
|
|
|
|
while(true)
|
|
|
|
{
|
|
|
|
json data = getJson("commentThreads?part=snippet,replies&allThreadsRelatedToChannelId=" + channelToTreat + "&maxResults=100&pageToken=" + pageToken);
|
|
|
|
bool doesRelyingOnCommentThreadsIsEnough = data["error"]["errors"][0]["reason"] != "commentsDisabled";
|
|
|
|
if(doesRelyingOnCommentThreadsIsEnough)
|
|
|
|
{
|
|
|
|
json items = data["items"];
|
|
|
|
for(const auto& item : items)
|
|
|
|
{
|
|
|
|
json comment = item["snippet"]["topLevelComment"];
|
|
|
|
string commentId = comment["id"];
|
|
|
|
treatComment(comment);
|
|
|
|
if(item.contains("replies"))
|
|
|
|
{
|
|
|
|
json replies = item["replies"]["comments"];
|
|
|
|
if(replies.size() >= 5)
|
|
|
|
{
|
|
|
|
string pageToken = "";
|
|
|
|
while(true)
|
|
|
|
{
|
|
|
|
json data = getJson("comments?part=snippet&parentId=" + commentId + "&maxResults=100&pageToken=" + pageToken);
|
|
|
|
json items = data["items"];
|
|
|
|
for(const auto& item : items)
|
|
|
|
{
|
|
|
|
treatComment(item);
|
|
|
|
}
|
|
|
|
if(data.contains("nextPageToken"))
|
|
|
|
{
|
|
|
|
pageToken = data["nextPageToken"];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for(const auto& reply : replies)
|
|
|
|
{
|
|
|
|
treatComment(reply);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(data.contains("nextPageToken"))
|
|
|
|
{
|
|
|
|
pageToken = data["nextPageToken"];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
PRINT("Comments disabled channel!")
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PRINT(commentsCount)
|
|
|
|
commentsCount = 0;
|
|
|
|
|
|
|
|
channelsToTreat.erase(channelToTreat);
|
|
|
|
channelsAlreadyTreated.insert(channelToTreat);
|
|
|
|
|
|
|
|
string channelToTreatDirectory = channelsDirectory + channelToTreat + "/";
|
|
|
|
createDirectory(channelToTreatDirectory);
|
|
|
|
|
|
|
|
string toWrite = (doesFileExist(channelsToTreatFilePath) ? "\n" : "") + channelToTreat;
|
|
|
|
writeFile(channelsToTreatFilePath, "a", toWrite);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2022-12-22 05:20:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void treatComment(json comment)
|
|
|
|
{
|
2022-12-22 06:18:22 +01:00
|
|
|
json snippet = comment["snippet"];
|
|
|
|
// The `else` case can happen (cf `95a9421ad0469a09335afeddb2983e31dc00bc36`).
|
|
|
|
if(snippet.contains("authorChannelId"))
|
|
|
|
{
|
|
|
|
string channelId = snippet["authorChannelId"]["value"];
|
|
|
|
if(find(channelsAlreadyTreated.begin(), channelsAlreadyTreated.end(), channelId) == channelsAlreadyTreated.end())
|
|
|
|
channelsToTreat.insert(channelId);
|
|
|
|
}
|
|
|
|
commentsCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool writeFile(string filePath, string option, string toWrite)
|
|
|
|
{
|
|
|
|
FILE* file = fopen(filePath.c_str(), option.c_str());
|
|
|
|
if(file != NULL)
|
|
|
|
{
|
|
|
|
fputs(toWrite.c_str(), file);
|
|
|
|
fclose(file);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool doesFileExist(string filePath)
|
|
|
|
{
|
|
|
|
struct stat buffer;
|
|
|
|
return stat(filePath.c_str(), &buffer) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void createDirectory(string path)
|
|
|
|
{
|
|
|
|
mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
|
2022-12-22 05:20:32 +01:00
|
|
|
}
|
|
|
|
|
2022-12-22 05:47:16 +01:00
|
|
|
string getDate()
|
|
|
|
{
|
|
|
|
auto t = time(nullptr);
|
|
|
|
auto tm = *localtime(&t);
|
|
|
|
ostringstream toString;
|
|
|
|
toString << put_time(&tm, "%d-%m-%Y %H-%M-%S");
|
|
|
|
return toString.str();
|
|
|
|
}
|
|
|
|
|
2022-12-22 05:20:32 +01:00
|
|
|
vector<string> getFileContent(string filePath)
|
|
|
|
{
|
2022-12-22 06:18:22 +01:00
|
|
|
vector<string> lines;
|
|
|
|
ifstream infile(filePath.c_str());
|
2022-12-22 05:20:32 +01:00
|
|
|
string line;
|
|
|
|
while(getline(infile, line))
|
2022-12-22 06:18:22 +01:00
|
|
|
lines.push_back(line);
|
2022-12-22 05:20:32 +01:00
|
|
|
return lines;
|
|
|
|
}
|
|
|
|
|
|
|
|
json getJson(string url)
|
|
|
|
{
|
2022-12-22 06:18:22 +01:00
|
|
|
url = "https://www.googleapis.com/youtube/v3/" + url + "&key=" + API_KEY;
|
|
|
|
string content = getHttps(url);
|
|
|
|
json data = json::parse(content);
|
|
|
|
return data;
|
2022-12-22 05:20:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void print(ostringstream* toPrint)
|
|
|
|
{
|
2022-12-22 06:18:22 +01:00
|
|
|
cout << getDate() << ": " << toPrint->str() << endl;
|
|
|
|
toPrint->str("");
|
2022-12-22 05:20:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
string getHttps(string url)
|
|
|
|
{
|
|
|
|
CURL* curl = curl_easy_init();
|
|
|
|
string got;
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
|
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &got);
|
|
|
|
curl_easy_perform(curl);
|
|
|
|
curl_easy_cleanup(curl);
|
|
|
|
return got;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t writeCallback(void* contents, size_t size, size_t nmemb, void* userp)
|
|
|
|
{
|
|
|
|
((string*)userp)->append((char*)contents, size * nmemb);
|
|
|
|
return size * nmemb;
|
|
|
|
}
|