├── .vscode ├── arduino.json └── c_cpp_properties.json ├── README.md ├── examples └── esp8266 │ ├── getStreamInfo │ └── getStreamInfo.ino │ └── userAndFollowerData │ └── userAndFollowerData.ino ├── library.properties └── src ├── TwitchApi.cpp └── TwitchApi.h /.vscode/arduino.json: -------------------------------------------------------------------------------- 1 | { 2 | "board": "esp8266:esp8266:d1", 3 | "configuration": "CpuFrequency=80,UploadSpeed=921600,FlashSize=4M3M", 4 | "port": "COM3" 5 | } -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "macFrameworkPath": [ 10 | "/System/Library/Frameworks", 11 | "/Library/Frameworks" 12 | ], 13 | "compilerPath": "/usr/bin/clang", 14 | "cStandard": "c11", 15 | "cppStandard": "c++17", 16 | "intelliSenseMode": "clang-x64" 17 | }, 18 | { 19 | "name": "Win32", 20 | "includePath": [ 21 | "C:\\Users\\Brian\\AppData\\Local\\Arduino15\\packages\\esp8266\\tools\\**", 22 | "C:\\Users\\Brian\\AppData\\Local\\Arduino15\\packages\\esp8266\\hardware\\esp8266\\2.4.0-rc1\\**" 23 | ], 24 | "forcedInclude": [], 25 | "intelliSenseMode": "msvc-x64", 26 | "compilerPath": "/usr/bin/clang", 27 | "cStandard": "c11", 28 | "cppStandard": "c++17" 29 | } 30 | ], 31 | "version": 4 32 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # arduino_twitch_api 2 | A wrapper for the [Twitch API](https://dev.twitch.tv/docs/api/reference/) for Arduino 3 | 4 | Interact with the Twitch API directly on your Arduino Device. Works on ESP8266 (tested), ESP32(not tested, but should) and probably anything else that supports HTTPS clients. 5 | 6 | ![Example](https://i.imgur.com/xMxX4YD.png) 7 | 8 | If you are new the ESP8266, [check out this video](https://www.youtube.com/watch?v=AFUAMVFzpWw) 9 | 10 | ## Implemented Endpoints 11 | 12 | - users (See userAndFollowerData example) 13 | - follower (For getting follower count) 14 | - stream (For getting live viewer count - See getStreamInfo) -------------------------------------------------------------------------------- /examples/esp8266/getStreamInfo/getStreamInfo.ino: -------------------------------------------------------------------------------- 1 | /******************************************************************* 2 | * Get User Data and follower data for a given twitch user 3 | * 4 | * By Brian Lough 5 | * https://www.youtube.com/channel/UCezJOfu7OtqGzd5xrP3q6WA 6 | *******************************************************************/ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include // This Sketch doesn't technically need this, but the library does so it must be installed. 13 | 14 | //------- Replace the following! ------ 15 | char ssid[] = "SSID"; // your network SSID (name) 16 | char password[] = "password"; // your network key 17 | 18 | // Create a new application on https://dev.twitch.tv/ 19 | #define TWITCH_CLIENT_ID "1234567890654rfsc" 20 | 21 | // Username of who you are getting the data for (e.g. "ninja") 22 | #define TWITCH_LOGIN "brianlough" 23 | 24 | WiFiClientSecure client; 25 | TwitchApi twitch(client, TWITCH_CLIENT_ID); 26 | 27 | unsigned long delayBetweenRequests = 60000; // Time between requests (1 minute) 28 | unsigned long requestDueTime; //time when request due 29 | 30 | void setup() 31 | { 32 | 33 | Serial.begin(115200); 34 | 35 | // kss._debug = true; 36 | 37 | // Set WiFi to station mode and disconnect from an AP if it was Previously 38 | // connected 39 | WiFi.mode(WIFI_STA); 40 | WiFi.disconnect(); 41 | delay(100); 42 | 43 | // Attempt to connect to Wifi network: 44 | Serial.print("Connecting Wifi: "); 45 | Serial.println(ssid); 46 | WiFi.begin(ssid, password); 47 | while (WiFi.status() != WL_CONNECTED) 48 | { 49 | Serial.print("."); 50 | delay(500); 51 | } 52 | Serial.println(""); 53 | Serial.println("WiFi connected"); 54 | Serial.println("IP address: "); 55 | IPAddress ip = WiFi.localIP(); 56 | Serial.println(ip); 57 | } 58 | 59 | void loop() 60 | { 61 | 62 | if (millis() > requestDueTime) 63 | { 64 | Serial.print("Free Heap: "); 65 | Serial.println(ESP.getFreeHeap()); 66 | 67 | Serial.print("Getting Stream info for:"); 68 | Serial.println(TWITCH_LOGIN); 69 | StreamInfo stream = twitch.getStreamInfo(TWITCH_LOGIN); 70 | if(!stream.error){ 71 | Serial.println("---------Stream Info ---------"); 72 | 73 | Serial.print("Id: "); 74 | Serial.println(stream.id); 75 | 76 | Serial.print("User Id: "); 77 | Serial.println(stream.userId); 78 | 79 | Serial.print("User Name: "); 80 | Serial.println(stream.userName); 81 | 82 | Serial.print("Game Id: "); 83 | Serial.println(stream.gameId); 84 | 85 | Serial.print("Type: "); 86 | Serial.println(stream.type); 87 | 88 | Serial.print("Title: "); 89 | Serial.println(stream.title); 90 | 91 | Serial.print("Viewer Count: "); 92 | Serial.println(stream.viewerCount); 93 | 94 | Serial.print("Started At: "); 95 | Serial.println(stream.startedAt); 96 | 97 | Serial.print("Language: "); 98 | Serial.println(stream.language); 99 | 100 | Serial.print("Thumbnail URL: "); 101 | Serial.println(stream.thumbnailUrl); 102 | Serial.println("------------------------"); 103 | } 104 | 105 | requestDueTime = millis() + delayBetweenRequests; 106 | } 107 | } -------------------------------------------------------------------------------- /examples/esp8266/userAndFollowerData/userAndFollowerData.ino: -------------------------------------------------------------------------------- 1 | /******************************************************************* 2 | * Get User Data and follower data for a given twitch user 3 | * 4 | * By Brian Lough 5 | * https://www.youtube.com/channel/UCezJOfu7OtqGzd5xrP3q6WA 6 | *******************************************************************/ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include // This Sketch doesn't technically need this, but the library does so it must be installed. 13 | 14 | //------- Replace the following! ------ 15 | char ssid[] = "SSID"; // your network SSID (name) 16 | char password[] = "password"; // your network key 17 | 18 | // Create a new application on https://dev.twitch.tv/ 19 | #define TWITCH_CLIENT_ID "1234567890654rfsc" 20 | 21 | // Username of who you are getting the data for (e.g. "ninja") 22 | #define TWITCH_LOGIN "brianlough" 23 | 24 | WiFiClientSecure client; 25 | TwitchApi twitch(client, TWITCH_CLIENT_ID); 26 | 27 | unsigned long delayBetweenRequests = 60000; // Time between requests (1 minute) 28 | unsigned long requestDueTime; //time when request due 29 | 30 | void setup() 31 | { 32 | 33 | Serial.begin(115200); 34 | 35 | // kss._debug = true; 36 | 37 | // Set WiFi to station mode and disconnect from an AP if it was Previously 38 | // connected 39 | WiFi.mode(WIFI_STA); 40 | WiFi.disconnect(); 41 | delay(100); 42 | 43 | // Attempt to connect to Wifi network: 44 | Serial.print("Connecting Wifi: "); 45 | Serial.println(ssid); 46 | WiFi.begin(ssid, password); 47 | while (WiFi.status() != WL_CONNECTED) 48 | { 49 | Serial.print("."); 50 | delay(500); 51 | } 52 | Serial.println(""); 53 | Serial.println("WiFi connected"); 54 | Serial.println("IP address: "); 55 | IPAddress ip = WiFi.localIP(); 56 | Serial.println(ip); 57 | } 58 | 59 | void loop() 60 | { 61 | 62 | if (millis() > requestDueTime) 63 | { 64 | Serial.print("Free Heap: "); 65 | Serial.println(ESP.getFreeHeap()); 66 | 67 | Serial.print("Getting Data for: "); 68 | Serial.println(TWITCH_LOGIN); 69 | UserData user = twitch.getUserData(TWITCH_LOGIN); 70 | if(!user.error){ 71 | Serial.println("---------User Details ---------"); 72 | Serial.print("Id: "); 73 | Serial.println(user.id); 74 | 75 | Serial.print("Login: "); 76 | Serial.println(user.login); 77 | 78 | Serial.print("Display Name: "); 79 | Serial.println(user.displayName); 80 | 81 | Serial.print("Broadcaster Type: "); 82 | Serial.println(user.broadCasterType); 83 | 84 | Serial.print("Description: "); 85 | Serial.println(user.description); 86 | 87 | Serial.print("Profile Image URL: "); 88 | Serial.println(user.profileImageUrl); 89 | 90 | Serial.print("Offline Image URL: "); 91 | Serial.println(user.offlineImageUrl); 92 | 93 | Serial.print("View Count: "); 94 | Serial.println(user.viewCount); 95 | Serial.println("------------------------"); 96 | 97 | // Follower data requires the user Id, this doesn't change 98 | // so you can hardcode it into the sketch after you get it once. 99 | // e.g. char *userId = "171235731"; 100 | 101 | FollowerData followerData = twitch.getFollowerData(user.id); 102 | if(!followerData.error){ 103 | Serial.println("---------Follower Data ---------"); 104 | 105 | Serial.print("Number of Followers: "); 106 | Serial.println(followerData.total); 107 | 108 | Serial.print("Last Follower Id: "); 109 | Serial.println(followerData.fromId); 110 | 111 | Serial.print("Last Follower Name: "); 112 | Serial.println(followerData.fromName); 113 | 114 | Serial.print("Last Follower to Id: "); 115 | Serial.println(followerData.toId); 116 | 117 | Serial.print("Last Follower to Name: "); 118 | Serial.println(followerData.toName); 119 | 120 | Serial.print("Last Follower at: "); 121 | Serial.println(followerData.followedAt); 122 | 123 | Serial.println("------------------------"); 124 | } 125 | } 126 | 127 | requestDueTime = millis() + delayBetweenRequests; 128 | } 129 | } -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=TwitchApi 2 | version=1.0.0 3 | author=Brian Lough 4 | maintainer=Brian Lough 5 | sentence=A library to wrap the Twitch API (supports ESP8266/ESP32 & others) 6 | paragraph=A library to wrap the Twitch API (supports ESP8266/ESP32 & others) 7 | category=Communication 8 | url=https://github.com/witnessmenow/arduino_twitch_api 9 | architectures=* -------------------------------------------------------------------------------- /src/TwitchApi.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Brian Lough. All right reserved. 3 | 4 | TwitchApi - An Arduino library to wrap the Twitch API 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Lesser General Public License for more details. 15 | 16 | You should have received a copy of the GNU Lesser General Public 17 | License along with this library; if not, write to the Free Software 18 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 | */ 20 | 21 | #include "TwitchApi.h" 22 | 23 | TwitchApi::TwitchApi(Client &client, char *clientId) 24 | { 25 | this->client = &client; 26 | this->_clientId = clientId; 27 | } 28 | 29 | bool TwitchApi::makeGetRequestWithClientId(char *command) 30 | { 31 | 32 | client->setTimeout(5000); 33 | if (!client->connect(TWITCH_HOST, portNumber)) 34 | { 35 | Serial.println(F("Connection failed")); 36 | return false; 37 | } 38 | 39 | //Serial.println(F("Connected!")); 40 | 41 | // Default client doesnt have a verify, need to figure something else out. 42 | // if (_checkFingerPrint && !client->verify(TWITCH_FINGERPRINT, TWITCH_HOST)) 43 | // { 44 | // Serial.println(F("certificate doesn't match")); 45 | // return false; 46 | // } 47 | 48 | // give the esp a breather 49 | yield(); 50 | 51 | // Send HTTP request 52 | client->print(F("GET ")); 53 | client->print(command); 54 | client->println(F(" HTTP/1.1")); 55 | 56 | //Headers 57 | client->print(F("Host: ")); 58 | client->println(TWITCH_HOST); 59 | 60 | client->print(F("Client-ID: ")); 61 | client->println(_clientId); 62 | 63 | client->println(F("Cache-Control: no-cache")); 64 | 65 | if (client->println() == 0) 66 | { 67 | Serial.println(F("Failed to send request")); 68 | return false; 69 | } 70 | 71 | // Check HTTP status 72 | char status[32] = {0}; 73 | client->readBytesUntil('\r', status, sizeof(status)); 74 | if (strcmp(status, "HTTP/1.1 200 OK") != 0) 75 | { 76 | Serial.print(F("Unexpected response: ")); 77 | Serial.println(status); 78 | return false; 79 | } 80 | 81 | // Skip HTTP headers 82 | char endOfHeaders[] = "\r\n\r\n"; 83 | if (!client->find(endOfHeaders)) 84 | { 85 | Serial.println(F("Invalid response")); 86 | return false; 87 | } 88 | 89 | // Let the caller of this method parse the JSon from the client 90 | return true; 91 | } 92 | 93 | UserData TwitchApi::getUserData(char *loginName) 94 | { 95 | char command[100] = "/helix/users?login="; 96 | strcat(command, loginName); 97 | if (_debug) { 98 | Serial.println(command); 99 | } 100 | 101 | // Use arduinojson.org/assistant to compute the capacity. 102 | const size_t bufferSize = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(9) + 1000; 103 | 104 | UserData user; 105 | user.error = true; 106 | if (makeGetRequestWithClientId(command)) 107 | { 108 | // Allocate JsonBuffer 109 | DynamicJsonBuffer jsonBuffer(bufferSize); 110 | 111 | // Parse JSON object 112 | JsonObject &root = jsonBuffer.parseObject(*client); 113 | if (root.success()) 114 | { 115 | // clang-format off 116 | JsonObject &data0 = root["data"][0]; 117 | user.id = (char *)data0["id"].as(); 118 | user.login = (char *)data0["login"].as(); 119 | user.displayName = (char *)data0["display_name"].as(); 120 | user.type = (char *)data0["type"].as(); 121 | user.broadCasterType = (char *)data0["broadcaster_type"].as(); 122 | user.description = (char *)data0["description"].as(); 123 | user.profileImageUrl = (char *)data0["profile_image_url"].as(); 124 | user.offlineImageUrl = (char *)data0["offline_image_url"].as(); 125 | user.viewCount = data0["view_count"].as(); 126 | user.error = false; 127 | // clang-format on 128 | } else { 129 | Serial.println(F("Parsing failed!")); 130 | } 131 | 132 | } 133 | closeClient(); 134 | return user; 135 | } 136 | 137 | FollowerData TwitchApi::getFollowerData(char *id) 138 | { 139 | char command[100] = "/helix/users/follows?to_id="; 140 | strcat(command, id); 141 | strcat(command, "&first=1"); 142 | if (_debug) { 143 | Serial.println(command); 144 | } 145 | 146 | // Use arduinojson.org/assistant to compute the capacity. 147 | const size_t bufferSize = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(5); 148 | 149 | FollowerData follower; 150 | follower.error = true; 151 | if (makeGetRequestWithClientId(command)) 152 | { 153 | // Allocate JsonBuffer 154 | DynamicJsonBuffer jsonBuffer(bufferSize); 155 | 156 | // Parse JSON object 157 | JsonObject &root = jsonBuffer.parseObject(*client); 158 | if (root.success()) 159 | { 160 | // clang-format off 161 | follower.total = root["total"].as(); 162 | JsonObject &data0 = root["data"][0]; 163 | follower.fromId = (char *)data0["from_id"].as(); 164 | follower.fromName = (char *)data0["from_name"].as(); 165 | follower.toId = (char *)data0["to_id"].as(); 166 | follower.toName = (char *)data0["to_name"].as(); 167 | follower.followedAt = (char *)data0["followed_at"].as(); 168 | follower.error = false; 169 | // clang-format on 170 | } else { 171 | Serial.println(F("Parsing failed!")); 172 | } 173 | } 174 | 175 | closeClient(); 176 | return follower; 177 | } 178 | 179 | StreamInfo TwitchApi::getStreamInfo(char *loginName) 180 | { 181 | char command[100] = "/helix/streams?user_login="; 182 | strcat(command, loginName); 183 | strcat(command, "&first=1"); 184 | if (_debug) { 185 | Serial.println(command); 186 | } 187 | 188 | // Use arduinojson.org/assistant to compute the capacity. 189 | const size_t bufferSize = JSON_ARRAY_SIZE(1) + JSON_ARRAY_SIZE(3) + JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(11); 190 | 191 | StreamInfo stream; 192 | stream.error = true; 193 | if (makeGetRequestWithClientId(command)) 194 | { 195 | // Allocate JsonBuffer 196 | DynamicJsonBuffer jsonBuffer(bufferSize); 197 | 198 | // Parse JSON object 199 | JsonObject &root = jsonBuffer.parseObject(*client); 200 | if (root.success()) 201 | { 202 | if(root["data"].size() > 0){ 203 | // clang-format off 204 | JsonObject &data0 = root["data"][0]; 205 | stream.id = (char *)data0["id"].as(); 206 | stream.userId = (char *)data0["user_id"].as(); 207 | stream.userName = (char *)data0["user_name"].as(); 208 | stream.gameId = (char *)data0["game_id"].as(); 209 | stream.type = (char *)data0["type"].as(); 210 | 211 | stream.title = (char *)data0["title"].as(); 212 | stream.viewerCount = data0["viewer_count"].as(); 213 | stream.startedAt = (char *)data0["started_at"].as(); 214 | stream.language = (char *)data0["language"].as(); 215 | stream.thumbnailUrl = (char *)data0["thumbnail_url"].as(); 216 | stream.error = false; 217 | // clang-format on 218 | } else { 219 | Serial.println(F("No Streams found!")); 220 | } 221 | } else { 222 | Serial.println(F("Parsing failed!")); 223 | } 224 | } 225 | 226 | closeClient(); 227 | return stream; 228 | } 229 | 230 | void TwitchApi::closeClient() { 231 | if (client->connected()) { 232 | if (_debug) { 233 | Serial.println(F("Closing client")); 234 | } 235 | client->stop(); 236 | } 237 | } -------------------------------------------------------------------------------- /src/TwitchApi.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2018 Brian Lough. All right reserved. 3 | 4 | TwitchApi - An Arduino library to wrap the Twitch API 5 | 6 | This library is free software; you can redistribute it and/or 7 | modify it under the terms of the GNU Lesser General Public 8 | License as published by the Free Software Foundation; either 9 | version 2.1 of the License, or (at your option) any later version. 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this library; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 17 | */ 18 | 19 | #ifndef TwitchApi_h 20 | #define TwitchApi_h 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #define TWITCH_HOST "api.twitch.tv" 27 | // Fingerprint correct as of October 6th 2018 28 | #define TWITCH_FINGERPRINT "BC 73 A5 9C 6E EE 38 43 A6 37 FC 32 CF 08 16 DC CF F1 5A 66" 29 | #define TWITCH_TIMEOUT 1500 30 | 31 | struct UserData 32 | { 33 | char *id; 34 | char *login; 35 | char *displayName; 36 | char *type; 37 | char *broadCasterType; 38 | char *description; 39 | char *profileImageUrl; 40 | char *offlineImageUrl; 41 | long viewCount; 42 | bool error; 43 | }; 44 | 45 | struct FollowerData 46 | { 47 | long total; 48 | char *fromId; 49 | char *fromName; 50 | char *toId; 51 | char *toName; 52 | char *followedAt; 53 | bool error; 54 | }; 55 | 56 | struct StreamInfo 57 | { 58 | char *id; 59 | char *userId; 60 | char *userName; 61 | char *gameId; 62 | char *type; 63 | char *title; 64 | long viewerCount; 65 | char *startedAt; 66 | char *language; 67 | char *thumbnailUrl; 68 | bool error; 69 | }; 70 | 71 | class TwitchApi 72 | { 73 | public: 74 | TwitchApi(Client &client, char *clientId); 75 | bool makeGetRequestWithClientId(char *command); 76 | UserData getUserData(char *loginName); 77 | FollowerData getFollowerData(char *id); 78 | StreamInfo getStreamInfo(char *loginName); 79 | int portNumber = 443; 80 | //bool _checkFingerPrint = true; //Fail request if fingerprint doesnt match 81 | bool _debug = false; 82 | Client *client; 83 | 84 | private: 85 | char *_clientId; 86 | void closeClient(); 87 | }; 88 | 89 | #endif --------------------------------------------------------------------------------