diff --git a/examples/ESP8266/MyRecentSubscribers/MyRecentSubscribers.ino b/examples/ESP8266/MyRecentSubscribers/MyRecentSubscribers.ino new file mode 100644 index 0000000..b5c587a --- /dev/null +++ b/examples/ESP8266/MyRecentSubscribers/MyRecentSubscribers.ino @@ -0,0 +1,67 @@ +/******************************************************************* + * Read YouTube Channel statistics from the YouTube API * + * This sketch uses the WiFiManager Library for configuraiton * + * * + * By Brian Lough * + * https://www.youtube.com/channel/UCezJOfu7OtqGzd5xrP3q6WA * + *******************************************************************/ + +#include +#include +#include + +#include // This Sketch doesn't technically need this, but the library does so it must be installed. + +//------- Replace the following! ------ +char channelId[30] = ""; +char clientId[80] = ""; +char clientSecret[120] = ""; +char refreshToken[80] = ""; + +WiFiClientSecure client; +YoutubeApi *api; + +unsigned long api_mtbs = 5000; //mean time between api requests +unsigned long api_lasttime; //last time api request has been done + +void setup() { + Serial.begin(115200); + + // Set WiFi to station mode and disconnect from an AP if it was Previously + // connected + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + delay(100); + + // Attempt to connect to Wifi network: + Serial.print("Connecting Wifi: "); + Serial.println(ssid); + WiFi.begin(ssid, password); + while (WiFi.status() != WL_CONNECTED) { + Serial.print("."); + delay(500); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + IPAddress ip = WiFi.localIP(); + Serial.println(ip); + + api = new YoutubeApi(client, clientId, clientSecret, refreshToken); +} + +void loop() { + if (millis() - api_lasttime > api_mtbs) { + String result = api->getMyRecentSubscribers(); // Returns the nextPageToken or the error message + Serial.println("--My Recent Subscribers--"); + for (int i = 0; i < sizeof(api->myRecentSubscribers)/sizeof(String); i++) { + Serial.println(api->myRecentSubscribers[i]); + } + Serial.print("Result (nextPageToken or error): "); + Serial.println(result); + Serial.println("------------------------"); + + api_lasttime = millis(); + } +} diff --git a/examples/ESP8266/MyRecentSubscribersWifiManager/MyRecentSubscribersWifiManager.ino b/examples/ESP8266/MyRecentSubscribersWifiManager/MyRecentSubscribersWifiManager.ino new file mode 100644 index 0000000..19bd196 --- /dev/null +++ b/examples/ESP8266/MyRecentSubscribersWifiManager/MyRecentSubscribersWifiManager.ino @@ -0,0 +1,181 @@ +/******************************************************************* + * Read YouTube Channel statistics from the YouTube API * + * This sketch uses the WiFiManager Library for configuraiton * + * * + * By Brian Lough * + * https://www.youtube.com/channel/UCezJOfu7OtqGzd5xrP3q6WA * + *******************************************************************/ + +#include +#include +#include + +// For storing configurations +#include "FS.h" +#include + +// WiFiManager Libraries +#include //Local DNS Server used for redirecting all rs to the configuration portal +#include //Local WebServer used to serve the configuration portal +#include //https://github.com/tzapu/WiFiManager WiFi Configuration Magic + +const int resetConfigPin = D8; //When high will reset the wifi manager config + +char channelId[30] = ""; +char clientId[80] = ""; +char clientSecret[120] = ""; +char refreshToken[80] = ""; + +WiFiClientSecure client; +YoutubeApi *api; + +unsigned long api_mtbs = 5000; //mean time between api requests +unsigned long api_lasttime; //last time api request has been done + +// flag for saving data +bool shouldSaveConfig = false; + +void saveConfigCallback(); +bool loadConfig(); +bool saveConfig(); +void forceConfigMode(); + +//callback notifying us of the need to save config +void saveConfigCallback() { + Serial.println("Should save config"); + shouldSaveConfig = true; +} + +void setup() { + + Serial.begin(115200); + + if (!SPIFFS.begin()) { + Serial.println("Failed to mount FS"); + return; + } + + pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output + digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (Note that LOW is the voltage level + loadConfig(); + + WiFiManager wifiManager; + wifiManager.setSaveConfigCallback(saveConfigCallback); + + // Adding an additional config on the WIFI manager webpage for the API Key and Channel ID + WiFiManagerParameter customChannelId("channelId", "Channel ID", channelId, 35); + WiFiManagerParameter customClientId("clientId", "Client ID", clientId, 85); + WiFiManagerParameter customClientSecret("clientSecret", "Client secret", clientSecret, 125); + WiFiManagerParameter customRefreshToken("refreshToken", "Refresh token", refreshToken, 85); + wifiManager.addParameter(&customChannelId); + wifiManager.addParameter(&customClientId); + wifiManager.addParameter(&customClientSecret); + wifiManager.addParameter(&customRefreshToken); + + // If it fails to connect it will create a YouTube-Counter access point + wifiManager.autoConnect("YouTube-Counter", "supersecret"); + + strcpy(channelId, customChannelId.getValue()); + strcpy(clientId, customClientId.getValue()); + strcpy(clientSecret, customClientSecret.getValue()); + strcpy(refreshToken, customRefreshToken.getValue()); + + if (shouldSaveConfig) { + saveConfig(); + } + + digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH + // Force Config mode if there is no API key + if(strcmp(clientId, "") > 0 || strcmp(clientSecret, "") > 0 || strcmp(refreshToken, "") > 0) { + Serial.println("Init YouTube API"); + api = new YoutubeApi(clientId, clientSecret, refreshToken, client); + } else { + Serial.println("Forcing Config Mode"); + forceConfigMode(); + } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + IPAddress ip = WiFi.localIP(); + Serial.println(ip); +} + +bool loadConfig() { + File configFile = SPIFFS.open("/config.json", "r"); + if (!configFile) { + Serial.println("Failed to open config file"); + return false; + } + + size_t size = configFile.size(); + if (size > 1024) { + Serial.println("Config file size is too large"); + return false; + } + + // Allocate a buffer to store contents of the file. + std::unique_ptr buf(new char[size]); + + configFile.readBytes(buf.get(), size); + + StaticJsonBuffer<200> jsonBuffer; + JsonObject& json = jsonBuffer.parseObject(buf.get()); + + if (!json.success()) { + Serial.println("Failed to parse config file"); + return false; + } + + strcpy(channelId, json["channelId"]); + strcpy(clientId, json["clientId"]); + strcpy(clientSecret, json["clientSecret"]); + strcpy(refreshToken, json["refreshToken"]); + return true; +} + +bool saveConfig() { + StaticJsonBuffer<200> jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + json["channelId"] = channelId; + json["clientId"] = clientId; + json["clientSecret"] = clientSecret; + json["refreshToken"] = refreshToken; + + File configFile = SPIFFS.open("/config.json", "w"); + if (!configFile) { + Serial.println("Failed to open config file for writing"); + return false; + } + + json.printTo(configFile); + return true; +} + +void forceConfigMode() { + Serial.println("Reset"); + WiFi.disconnect(); + Serial.println("Dq"); + delay(500); + ESP.restart(); + delay(5000); +} + +void loop() { + if(digitalRead(resetConfigPin) == HIGH) { + forceConfigMode(); + } + + if (millis() - api_lasttime > api_mtbs) { + String result = api->getMyRecentSubscribers(); // Returns the nextPageToken or the error message + Serial.println("--My Recent Subscribers--"); + for (int i = 0; i < sizeof(api->myRecentSubscribers)/sizeof(String); i++) { + Serial.println(api->myRecentSubscribers[i]); + } + Serial.print("Result (nextPageToken or error): "); + Serial.println(result); + Serial.println("------------------------"); + + api_lasttime = millis(); + } +} diff --git a/examples/ESP8266/MyRecentSubscribersWithOTA/MyRecentSubscribersWithOTA.ino b/examples/ESP8266/MyRecentSubscribersWithOTA/MyRecentSubscribersWithOTA.ino new file mode 100644 index 0000000..db4e067 --- /dev/null +++ b/examples/ESP8266/MyRecentSubscribersWithOTA/MyRecentSubscribersWithOTA.ino @@ -0,0 +1,213 @@ +/******************************************************************* + * Read YouTube Channel statistics from the YouTube API * + * This sketch uses the WiFiManager Library for configuraiton * + * * + * By Brian Lough * + * https://www.youtube.com/channel/UCezJOfu7OtqGzd5xrP3q6WA * + *******************************************************************/ + +#include +#include +#include + +// For storing configurations +#include "FS.h" +#include + +// WiFiManager Libraries +#include //Local DNS Server used for redirecting all rs to the configuration portal +#include //Local WebServer used to serve the configuration portal +#include //https://github.com/tzapu/WiFiManager WiFi Configuration Magic + +// OTA Stuff +#include +#include +#include + +const int resetConfigPin = D8; //When high will reset the wifi manager config + +char channelId[30] = ""; +char clientId[80] = ""; +char clientSecret[120] = ""; +char refreshToken[80] = ""; + +WiFiClientSecure client; +YoutubeApi *api; + +unsigned long api_mtbs = 5000; //mean time between api requests +unsigned long api_lasttime; //last time api request has been done + +// flag for saving data +bool shouldSaveConfig = false; + +void saveConfigCallback(); +bool loadConfig(); +bool saveConfig(); +void forceConfigMode(); + +//callback notifying us of the need to save config +void saveConfigCallback() { + Serial.println("Should save config"); + shouldSaveConfig = true; +} + +void setup() { + + Serial.begin(115200); + + if (!SPIFFS.begin()) { + Serial.println("Failed to mount FS"); + return; + } + + pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output + digitalWrite(LED_BUILTIN, LOW); // Turn the LED on (Note that LOW is the voltage level + loadConfig(); + + WiFiManager wifiManager; + wifiManager.setSaveConfigCallback(saveConfigCallback); + + // Adding an additional config on the WIFI manager webpage for the API Key and Channel ID + WiFiManagerParameter customChannelId("channelId", "Channel ID", channelId, 35); + WiFiManagerParameter customClientId("clientId", "Client ID", clientId, 85); + WiFiManagerParameter customClientSecret("clientSecret", "Client secret", clientSecret, 125); + WiFiManagerParameter customRefreshToken("refreshToken", "Refresh token", refreshToken, 85); + wifiManager.addParameter(&customChannelId); + wifiManager.addParameter(&customClientId); + wifiManager.addParameter(&customClientSecret); + wifiManager.addParameter(&customRefreshToken); + + // If it fails to connect it will create a YouTube-Counter access point + wifiManager.autoConnect("YouTube-Counter", "supersecret"); + + strcpy(channelId, customChannelId.getValue()); + strcpy(clientId, customClientId.getValue()); + strcpy(clientSecret, customClientSecret.getValue()); + strcpy(refreshToken, customRefreshToken.getValue()); + + if (shouldSaveConfig) { + saveConfig(); + } + + digitalWrite(LED_BUILTIN, HIGH); // Turn the LED off by making the voltage HIGH + // Force Config mode if there is no API key + if(strcmp(clientId, "") > 0 || strcmp(clientSecret, "") > 0 || strcmp(refreshToken, "") > 0) { + Serial.println("Init YouTube API"); + api = new YoutubeApi(clientId, clientSecret, refreshToken, client); + } else { + Serial.println("Forcing Config Mode"); + forceConfigMode(); + } + + ArduinoOTA.onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) + type = "sketch"; + else // U_SPIFFS + type = "filesystem"; + + // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() + Serial.println("Start updating " + type); + }); + ArduinoOTA.onEnd([]() { + Serial.println("\nEnd"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + ArduinoOTA.begin(); + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + IPAddress ip = WiFi.localIP(); + Serial.println(ip); +} + +bool loadConfig() { + File configFile = SPIFFS.open("/config.json", "r"); + if (!configFile) { + Serial.println("Failed to open config file"); + return false; + } + + size_t size = configFile.size(); + if (size > 1024) { + Serial.println("Config file size is too large"); + return false; + } + + // Allocate a buffer to store contents of the file. + std::unique_ptr buf(new char[size]); + + configFile.readBytes(buf.get(), size); + + StaticJsonBuffer<200> jsonBuffer; + JsonObject& json = jsonBuffer.parseObject(buf.get()); + + if (!json.success()) { + Serial.println("Failed to parse config file"); + return false; + } + + strcpy(channelId, json["channelId"]); + strcpy(clientId, json["clientId"]); + strcpy(clientSecret, json["clientSecret"]); + strcpy(refreshToken, json["refreshToken"]); + return true; +} + +bool saveConfig() { + StaticJsonBuffer<200> jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + json["channelId"] = channelId; + json["clientId"] = clientId; + json["clientSecret"] = clientSecret; + json["refreshToken"] = refreshToken; + + File configFile = SPIFFS.open("/config.json", "w"); + if (!configFile) { + Serial.println("Failed to open config file for writing"); + return false; + } + + json.printTo(configFile); + return true; +} + +void forceConfigMode() { + Serial.println("Reset"); + WiFi.disconnect(); + Serial.println("Dq"); + delay(500); + ESP.restart(); + delay(5000); +} + +void loop() { + if(digitalRead(resetConfigPin) == HIGH) { + forceConfigMode(); + } + + if (millis() - api_lasttime > api_mtbs) { + String result = api->getMyRecentSubscribers(); // Returns the nextPageToken or the error message + Serial.println("--My Recent Subscribers--"); + for (int i = 0; i < sizeof(api->myRecentSubscribers)/sizeof(String); i++) { + Serial.println(api->myRecentSubscribers[i]); + } + Serial.print("Result (nextPageToken or error): "); + Serial.println(result); + Serial.println("------------------------"); + + api_lasttime = millis(); + } + ArduinoOTA.handle(); // enables OTA, don't remove +} diff --git a/library.properties b/library.properties index af778d5..406bcf8 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,8 @@ -name=YoutubeApi +name=YoutubeApiOAuth2 version=1.0.0 -author=Brian Lough -maintainer=Brian Lough -sentence=A wrapper for the YouTube API for Arduino (supports ESP8266 & WiFi101 boards) -paragraph=Use this library to get YouTube channel statistics +author=marcfon +sentence=See the original library https://github.com/witnessmenow/arduino-youtube-api for documentation. +paragraph=Use this library to get YouTube channel statistics using OAuth2 category=Communication -url=https://github.com/witnessmenow/arduino-youtube-api +url=https://github.com/marcfon/arduino-youtube-api architectures=* diff --git a/src/YoutubeApi.cpp b/src/YoutubeApi.cpp index c400e61..f842335 100644 --- a/src/YoutubeApi.cpp +++ b/src/YoutubeApi.cpp @@ -21,72 +21,104 @@ #include "YoutubeApi.h" -YoutubeApi::YoutubeApi(String apiKey, Client &client) { - _apiKey = apiKey; +YoutubeApi::YoutubeApi(Client &client, String clientId, String clientSecret, String refreshToken) { this->client = &client; + _clientId = clientId; + _clientSecret = clientSecret; + oAuth2Token.refreshToken = refreshToken; + oAuth2Token.expiresIn = 0; + oAuth2Token.lastRefreshedAt = 0; } String YoutubeApi::sendGetToYoutube(String command) { - String headers=""; - String body=""; + if(getAccessToken()) { + // Connect with youtube api over ssl + if(client->connect(YTAPI_HOST, YTAPI_SSL_PORT)) { + command = "https://" YTAPI_HOST + command + "&access_token=" + oAuth2Token.accessToken; + client->println("GET " + command); + return readRequestResponse(); + } + return F("Failed to connect to YouTube."); + } + return F("Failed to retrieve valid access token."); +} + +/** + * This method sends a HTTP POST to the YouTube API host and is used for + * instance to refresh the OAuth2 access token. + * + * Reference: + * http://playground.arduino.cc/Code/WebClient + **/ +String YoutubeApi::sendPostToYouTube(String page, String postData) { + if(client->connect(YTAPI_HOST, YTAPI_SSL_PORT)) { + client->println("POST " + page + " HTTP/1.1"); + client->println("Host: " YTAPI_HOST); + client->println(F("Content-Type: application/x-www-form-urlencoded")); + client->println(F("Connection: close")); + client->print(F("Content-Length: ")); + client->println(postData.length()); + client->println(); + client->println(postData); + return readRequestResponse(); + } + return F("Failed to connect to YouTube."); +} + +String YoutubeApi::readRequestResponse() { + String headers = ""; + String body = ""; bool finishedHeaders = false; bool currentLineIsBlank = true; unsigned long now; bool avail; - // Connect with youtube api over ssl - if (client->connect(YTAPI_HOST, YTAPI_SSL_PORT)) { - // Serial.println(".... connected to server"); - String a=""; - char c; - int ch_count=0; - client->println("GET "+command+"&key="+_apiKey); - now=millis(); - avail=false; - while (millis() - now < YTAPI_TIMEOUT) { - while (client->available()) { - - // Allow body to be parsed before finishing - avail = finishedHeaders; - char c = client->read(); - //Serial.write(c); - - if(!finishedHeaders){ - if (currentLineIsBlank && c == '\n') { - finishedHeaders = true; - } - else{ - headers = headers + c; - - } - } else { - if (ch_count < maxMessageLength) { - body=body+c; - ch_count++; - } - } - - if (c == '\n') { - currentLineIsBlank = true; - }else if (c != '\r') { - currentLineIsBlank = false; - } - } - if (avail) { - //Serial.println("Body:"); - //Serial.println(body); - //Serial.println("END"); - break; - } - } - } - + String a = ""; + char c; + int ch_count = 0; + now = millis(); + avail = false; + + while(millis() - now < YTAPI_TIMEOUT) { + while(client->available()) { + // Allow body to be parsed before finishing + avail = finishedHeaders; + char c = client->read(); + + if(!finishedHeaders) { + if(currentLineIsBlank && c == '\n') { + finishedHeaders = true; + } + else { + headers = headers + c; + } + } else { + if(ch_count < maxMessageLength) { + body = body + c; + ch_count++; + } + } + if(c == '\n') { + currentLineIsBlank = true; + } else if(c != '\r') { + currentLineIsBlank = false; + } + } + if(avail) { + // Serial.println("Body:"); + // Serial.println(body); + // Serial.println("END"); + break; + } + } return body; } -bool YoutubeApi::getChannelStatistics(String channelId){ - String command="https://" YTAPI_HOST "/youtube/v3/channels?part=statistics&id="+channelId; //If you can't find it(for example if you have a custom url) look here: https://www.youtube.com/account_advanced - //Serial.println(command); - String response = sendGetToYoutube(command); //recieve reply from youtube +/** + * https://developers.google.com/youtube/v3/docs/channels/list + */ +bool YoutubeApi::getChannelStatistics(String channelId) { + String command = "/youtube/v3/channels?part=statistics&id=" + channelId; //If you can't find it(for example if you have a custom url) look here: https://www.youtube.com/account_advanced + String response = sendGetToYoutube(command); //receive reply from youtube DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.parseObject(response); if(root.success()) { @@ -106,6 +138,87 @@ bool YoutubeApi::getChannelStatistics(String channelId){ return true; } } - return false; } + +/** + * This method returns the most recent subscribers to the channel. + * + * TODO This method should eventually be implemented as getRecentSubscribers() + * with all the available parameters documented in the YouTube API. + * + * The pageToken is the String for the next (or previous) set of subscribers. + * + * Return the next page string token if succesfull otherwise the error + * message that was returned by the YouTube API + * + * References + * https://www.googleapis.com/youtube/v3/subscriptions + * https://developers.google.com/youtube/v3/docs/subscriptions/list + */ +String YoutubeApi::getMyRecentSubscribers(String pageToken) { + // FIXME there is a bug in the youtube api that seems to ignore the pageToken + // and just returns the first page https://issuetracker.google.com/issues/35176305 + + // We specify which fields should be returned which minimizes the size of the JSON response + // and saves valuable memory space + String command = "/youtube/v3/subscriptions?part=subscriberSnippet&myRecentSubscribers=true&maxResults=" + String(sizeof(myRecentSubscribers)/sizeof(String)) + "&fields=items%2FsubscriberSnippet%2Ftitle%2CnextPageToken%2CprevPageToken&prevPageToken=" + pageToken; + String response = sendGetToYoutube(command); + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.parseObject(response); + if(root.success()) { + if (root.containsKey("items")) { + for (int i = 0; i < root["items"].size(); i++) { + String subscriber = root["items"][i]["subscriberSnippet"]["title"]; + myRecentSubscribers[i] = subscriber; + } + return root["nextPageToken"]; + } + } + String code = root["error"]["code"]; + String message = root["error"]["message"]; + return "error," + code + "," + message; +} + +String YoutubeApi::getMyRecentSubscribers() { + return getMyRecentSubscribers(""); +} + +/** + * The access token is only valid for 3600 seconds (by default) before it needs + * to be refreshed. + */ +bool YoutubeApi::getAccessToken() { + unsigned long currentTime = millis(); + unsigned long tokenExpiresAt = oAuth2Token.lastRefreshedAt + (oAuth2Token.expiresIn * 1000); + + String page = "/oauth2/v4/token"; // https://developers.google.com/youtube/v3/guides/auth/devices + String myClientId(_clientId); // TODO Is there a better way to turn char[] into a String? + String myClientSecret(_clientSecret); + String postData = "client_id=" + myClientId + "&client_secret=" + myClientSecret + "&refresh_token=" + oAuth2Token.refreshToken + "&grant_type=refresh_token"; + + // Only get a new access token if the old one is expired + if(currentTime > tokenExpiresAt) { + String response = sendPostToYouTube(page, postData); + DynamicJsonBuffer jsonBuffer; + JsonObject& root = jsonBuffer.parseObject(response); + if(root.success()) { + if(!root.containsKey("error")) { + String accessToken = root["access_token"]; + String tokenType = root["token_type"]; + int expiresIn = root["expires_in"]; + + oAuth2Token.accessToken = accessToken; + oAuth2Token.tokenType = tokenType; + oAuth2Token.expiresIn = expiresIn; + oAuth2Token.lastRefreshedAt = millis(); + + return true; + } + root.printTo(Serial); + } + return false; + } + // Serial.println("Don't need to refresh the token. Current access token is still valid!"); + return true; +} diff --git a/src/YoutubeApi.h b/src/YoutubeApi.h index 207e5e2..ba65036 100644 --- a/src/YoutubeApi.h +++ b/src/YoutubeApi.h @@ -28,10 +28,9 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA #define YTAPI_HOST "www.googleapis.com" #define YTAPI_SSL_PORT 443 -#define YTAPI_TIMEOUT 1500 +#define YTAPI_TIMEOUT 2000 - -struct channelStatistics{ +struct channelStatistics { long viewCount; long commentCount; long subscriberCount; @@ -39,19 +38,35 @@ struct channelStatistics{ long videoCount; }; +struct token { + String accessToken; + String tokenType; + int expiresIn; + String refreshToken; + unsigned long lastRefreshedAt; +}; + class YoutubeApi { public: - YoutubeApi (String apiKey, Client &client); - String sendGetToYoutube(String command); + YoutubeApi(Client &client, String clientId, String clientSecret, String refreshToken); bool getChannelStatistics(String channelId); + String getMyRecentSubscribers(); + String getMyRecentSubscribers(String pageToken); channelStatistics channelStats; + String myRecentSubscribers[5]; // Fixed number for now, shouldn't go above 50 private: - String _apiKey; + String _clientId; + String _clientSecret; Client *client; - const int maxMessageLength = 1000; + token oAuth2Token; + const int maxMessageLength = 5000; bool checkForOkResponse(String response); + String sendGetToYoutube(String command); + String sendPostToYouTube(String page, String postData); + String readRequestResponse(); + bool getAccessToken(); }; #endif