";
141 | t += "";
142 | t += "";
143 | t += "";
144 | t += "";
145 | t += "";
146 | t += "Searching Twitter for: " + String(search_str.c_str()) + "
";
147 | t += "Latest Message: " + String(search_msg.c_str()) + "
";
148 | t += "
Update Search Term?";
149 | t += "";
150 | t += "";
151 | t += "";
152 | server.send(200, "text/html", t);
153 | }
154 |
155 | void processReadTweet(){
156 | if (server.args() > 0 and server.method() == HTTP_POST) { // Arguments were received
157 | for ( uint8_t i = 0; i < server.args(); i++ ) {
158 | Serial.print(server.argName(i)); // Display the argument
159 | if (server.argName(i) == "search_input") {
160 | search_str=std::string(server.arg(i).c_str());
161 | }
162 | }
163 | }
164 |
165 | String t;
166 | t += "";
167 | t += "";
168 | t += "";
169 | t += "";
170 | t += "";
171 | t += "";
172 | t += "Updated search term: " + String(search_str.c_str()) + "
";
173 | t += "
Update again?";
174 | t += "";
175 | t += "";
176 | t += "";
177 | server.send(200, "text/html", t);
178 | }
179 |
180 | void handleNotFound(){
181 | String message = "File Not Found\n\n";
182 | message += "URI: ";
183 | message += server.uri();
184 | message += "\nMethod: ";
185 | message += (server.method() == HTTP_GET)?"GET":"POST";
186 | message += "\nArguments: ";
187 | message += server.args();
188 | message += "\n";
189 | for (uint8_t i=0; i api_lasttime + api_mtbs) {
248 | digitalWrite(LED_BUILTIN, LOW);
249 | extractJSON(tcr.searchTwitter(search_str));
250 | Serial.print("Search: ");
251 | Serial.println(search_str.c_str());
252 | Serial.print("MSG: ");
253 | Serial.println(search_msg.c_str());
254 | api_lasttime = millis();
255 | }
256 | delay(2);
257 | yield();
258 | digitalWrite(LED_BUILTIN, HIGH);
259 | }
--------------------------------------------------------------------------------
/examples/esp8266/TwitterTweetSearch/secret.h:
--------------------------------------------------------------------------------
1 | //WiFi
2 | #define WIFICONFIG
3 | #ifdef WIFICONFIG
4 | const char* ssid = "wifi_ssid"; // WiFi SSID
5 | const char* password = "wifi_password"; // WiFi Password
6 | #endif
7 |
8 |
9 | // Twitter info
10 | #define TWITTERINFO
11 | // Values below are just a placeholder
12 | #ifdef TWITTERINFO // Obtain these by creating an app @ https://apps.twitter.com/
13 | static char const consumer_key[] = "gkyjeH3EF32NJfiuheuyf8623";
14 | static char const consumer_sec[] = "HbY5h$N86hg5jjd987HGFsRjJcMkjLaJw44628sOh353gI3H23";
15 | static char const accesstoken[] = "041657084136508135-F3BE63U4Y6b346kj6bnkdlvnjbGsd3V";
16 | static char const accesstoken_sec[] = "bsekjH8YT3dCWDdsgsdHUgdBiosesDgv43rknU4YY56Tj";
17 | #endif
--------------------------------------------------------------------------------
/examples/esp8266/TwitterTweetSearchFSWiFiMgr/TwitterTweetSearchFSWiFiMgr.ino:
--------------------------------------------------------------------------------
1 | #include // FS.h has to be first
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include // https://github.com/bblanchon/ArduinoJson
13 | //#include "secret.h" // uncomment if using secret.h file with credentials
14 | //#define TWI_TIMEOUT 3000 // varies depending on network speed (msec), needs to be before TwitterWebAPI.h
15 | #include
16 |
17 | // Choose one of the options below for MAX7219 Display
18 | //#define MAX7219DISPLAY // uncomment if using MAX7219-4-digit-display-for-ESP8266
19 | //#define MD_PAROLA_DISPLAY // uncomment if using MD Parola Library for MAX7219
20 |
21 | #ifdef MAX7219DISPLAY
22 | #include // https://github.com/SensorsIot/MAX7219-4-digit-display-for-ESP8266
23 | // VCC -> 5V, GND -> GND, DIN -> D7, CS -> D8 (configurable below), CLK -> D5
24 | const byte chips = 4; // Number of Display Chips
25 | MAX7219_Dot_Matrix display (chips, D8); // Chips / LOAD
26 | unsigned long MOVE_INTERVAL = 20; // (msec) increase to slow, decrease to fast
27 | #endif
28 |
29 | #ifdef MD_PAROLA_DISPLAY
30 | #include // https://github.com/MajicDesigns/MD_Parola
31 | #include // https://github.com/MajicDesigns/MD_MAX72xx
32 | #include // ^ edit MD_MAX72xx.h #define USE_PAROLA_HW 0 and #define USE_FC16_HW 1 if using FC16 dotmatrix display
33 | // VCC -> 5V, GND -> GND, DIN -> D7, CS -> D8, CLK -> D5
34 | #define MAX_DEVICES 4
35 | #define CLK_PIN D5
36 | #define DATA_PIN D7
37 | #define CS_PIN D8
38 | #endif
39 |
40 | bool resetsettings = false; // true to reset WiFiManager & delete FS files
41 | const char *HOSTNAME= "TwitterDisplay"; // Hostname of your device
42 | std::string search_str = "#dog"; // Default search word for twitter
43 | const char *ntp_server = "pool.ntp.org"; // time1.google.com, time.nist.gov, pool.ntp.org
44 | int timezone = -4; // US Eastern timezone -05:00 HRS
45 | unsigned long twi_update_interval = 20; // (seconds) minimum 5s (180 API calls/15 min). Any value less than 5 is ignored!
46 |
47 | // Values below are just a placeholder
48 | #ifndef TWITTERINFO // Obtain these by creating an app @ https://apps.twitter.com/
49 | static char const consumer_key[] = "gkyjeH3EF32NJfiuheuyf8623";
50 | static char const consumer_sec[] = "HbY5h$N86hg5jjd987HGFsRjJcMkjLaJw44628sOh353gI3H23";
51 | static char const accesstoken[] = "041657084136508135-F3BE63U4Y6b346kj6bnkdlvnjbGsd3V";
52 | static char const accesstoken_sec[] = "bsekjH8YT3dCWDdsgsdHUgdBiosesDgv43rknU4YY56Tj";
53 | #endif
54 | #ifndef AutoAP_password
55 | #define AutoAP_password "password" // Dafault AP Password
56 | #endif
57 | #ifndef ota_location
58 | #define ota_location "/firmware" // OTA update location
59 | #endif
60 | #ifndef ota_user
61 | #define ota_user "admin" // OTA username
62 | #endif
63 | #ifndef ota_password
64 | #define ota_password "password" // OTA password
65 | #endif
66 |
67 | // Dont change anything below this line //
68 | ///////////////////////////////////////////////
69 |
70 | #if defined(MAX7219DISPLAY) and defined(MD_PAROLA_DISPLAY)
71 | #error "Cant have both MAX7219DISPLAY and MD_PAROLA_DISPLAY enabled."
72 | #endif
73 |
74 | unsigned long api_mtbs = twi_update_interval * 1000; //mean time between api requests
75 | unsigned long api_lasttime = 0;
76 | bool twit_update = false;
77 | std::string search_msg = "No Message Yet!";
78 | #ifdef MAX7219DISPLAY
79 | unsigned long lastMoved = 0;
80 | int messageOffset;
81 | #endif
82 | #ifdef MD_PAROLA_DISPLAY
83 | MD_Parola P = MD_Parola(DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
84 | char curmsg[512];
85 | #endif
86 |
87 | WiFiUDP ntpUDP;
88 | NTPClient timeClient(ntpUDP, ntp_server, timezone*3600, 60000); // NTP server pool, offset (in seconds), update interval (in milliseconds)
89 | TwitterClient tcr(timeClient, consumer_key, consumer_sec, accesstoken, accesstoken_sec);
90 | ESP8266WebServer server(80);
91 | ESP8266HTTPUpdateServer httpUpdater;
92 |
93 | // Helper
94 | #define MODEBUTTON 0
95 | #define LED_BUILTIN 2
96 | #define SERIALDEBUG true
97 |
98 | #ifdef SERIALDEBUG
99 | #define DEBUG_PRINT(x) Serial.print(x)
100 | #define DEBUG_PRINTLN(x) Serial.println(x)
101 | #define DEBUG_PRINTF(x,y) Serial.printf(x,y)
102 | #else
103 | #define DEBUG_PRINT(x)
104 | #define DEBUG_PRINTLN(x)
105 | #define DEBUG_PRINTF(x,y)
106 | #endif
107 |
108 | String convertUnicode(String unicodeStr){
109 | String out = "";
110 | char iChar;
111 | char* error;
112 | for (int i = 0; i < unicodeStr.length(); i++){
113 | iChar = unicodeStr[i];
114 | if(iChar == '\\'){ // got escape char
115 | iChar = unicodeStr[++i];
116 | if(iChar == 'u'){ // got unicode hex
117 | char unicode[6];
118 | unicode[0] = '0';
119 | unicode[1] = 'x';
120 | for (int j = 0; j < 4; j++){
121 | iChar = unicodeStr[++i];
122 | unicode[j + 2] = iChar;
123 | }
124 | long unicodeVal = strtol(unicode, &error, 16); //convert the string
125 | out += (char)unicodeVal;
126 | } else if(iChar == '/'){
127 | out += iChar;
128 | } else if(iChar == 'n'){
129 | out += '\n';
130 | }
131 | } else {
132 | out += iChar;
133 | }
134 | }
135 | return out;
136 | }
137 |
138 | // flag for saving data
139 | bool shouldSaveConfig = false;
140 |
141 | // callback notifying us of the need to save config
142 | void saveConfigCallback () {
143 | DEBUG_PRINTLN("Should save config");
144 | shouldSaveConfig = true;
145 | }
146 |
147 | bool updateFS = false;
148 | // Write search_str to FS
149 | bool writetoFS(bool saveConfig){
150 | if (saveConfig) {
151 | //FS save
152 | DEBUG_PRINT("Mounting FS...");
153 | if (SPIFFS.begin() and saveConfig) {
154 | updateFS = true;
155 | DEBUG_PRINTLN("Mounted.");
156 | //save the custom parameters to FS
157 | DEBUG_PRINT("Saving config: ");
158 | // DynamicJsonBuffer jsonBuffer;
159 | StaticJsonBuffer<200> jsonBuffer;
160 | JsonObject& json = jsonBuffer.createObject();
161 | json["search"] = search_str.c_str();
162 |
163 | // SPIFFS.remove("/config.json") ? DEBUG_PRINTLN("removed file") : DEBUG_PRINTLN("failed removing file");
164 |
165 | File configFile = SPIFFS.open("/config.json", "w");
166 | if (!configFile) DEBUG_PRINTLN("failed to open config file for writing");
167 |
168 | json.printTo(Serial);
169 | json.printTo(configFile);
170 | configFile.close();
171 | updateFS = false;
172 | SPIFFS.end();
173 | return true;
174 | //end save
175 | } else {
176 | DEBUG_PRINTLN("Failed to mount FS");
177 | // SPIFFS.end();
178 | return false;
179 | }
180 | } else {
181 | DEBUG_PRINTLN("SaveConfig is False!");
182 | // SPIFFS.end();
183 | return false;
184 | }
185 | }
186 |
187 | // Read search_str to FS
188 | bool readfromFS() {
189 | //read configuration from FS json
190 | DEBUG_PRINT("Mounting FS...");
191 | updateFS = true;
192 | if (resetsettings) { SPIFFS.begin(); SPIFFS.remove("/config.json"); SPIFFS.format(); delay(1000);}
193 | if (SPIFFS.begin()) {
194 | DEBUG_PRINTLN("mounted file system");
195 | if (SPIFFS.exists("/config.json")) {
196 | //file exists, reading and loading
197 | DEBUG_PRINTLN("Reading config file");
198 | File configFile = SPIFFS.open("/config.json", "r");
199 | if (configFile) {
200 | DEBUG_PRINTLN("Opened config file");
201 | size_t size = configFile.size();
202 | // Allocate a buffer to store contents of the file.
203 | std::unique_ptr buf(new char[size]);
204 |
205 | configFile.readBytes(buf.get(), size);
206 | // DynamicJsonBuffer jsonBuffer;
207 | StaticJsonBuffer jsonBuffer;
208 | JsonObject& json = jsonBuffer.parseObject(buf.get());
209 | json.printTo(Serial);
210 | if (json.success()) {
211 | DEBUG_PRINTLN("\nparsed json");
212 | String tmpstr = json["search"];
213 | search_str = std::string(tmpstr.c_str(), tmpstr.length());
214 | SPIFFS.end();
215 | updateFS = false;
216 | return true;
217 | } else {
218 | DEBUG_PRINTLN("Failed to load json config");
219 | }
220 | } else {
221 | DEBUG_PRINTLN("Failed to open /config.json");
222 | }
223 | } else {
224 | DEBUG_PRINTLN("Coudnt find config.json");
225 | }
226 | } else {
227 | DEBUG_PRINTLN("Failed to mount FS");
228 | }
229 | //end read
230 | updateFS = false;
231 | return false;
232 | }
233 |
234 | void extractJSON(String tmsg) {
235 | const char* msg2 = const_cast (tmsg.c_str());
236 | DynamicJsonBuffer jsonBuffer;
237 | JsonObject& response = jsonBuffer.parseObject(msg2);
238 |
239 | if (!response.success()) {
240 | DEBUG_PRINTLN("Failed to parse JSON!");
241 | DEBUG_PRINTLN(msg2);
242 | // jsonBuffer.clear();
243 | return;
244 | }
245 |
246 | if (response.containsKey("statuses")) {
247 | String usert = response["statuses"][0]["user"]["screen_name"];
248 | String text = response["statuses"][0]["text"];
249 | if (text != "") {
250 | text = "@" + usert + " says " + text;
251 | search_msg = std::string(text.c_str(), text.length());
252 | }
253 | } else if(response.containsKey("errors")) {
254 | String err = response["errors"][0];
255 | search_msg = std::string(err.c_str(), err.length());
256 | } else {
257 | DEBUG_PRINTLN("No useful data");
258 | }
259 |
260 | jsonBuffer.clear();
261 | delete [] msg2;
262 | }
263 |
264 | void extractTweetText(String tmsg) {
265 | DEBUG_PRINT("Recieved Message Length");
266 | long msglen = tmsg.length();
267 | DEBUG_PRINT(": ");
268 | DEBUG_PRINTLN(msglen);
269 | // DEBUG_PRINT("MSG: ");
270 | // DEBUG_PRINTLN(tmsg);
271 | if (msglen <= 31) return;
272 |
273 | String searchstr = ",\"text\":\"";
274 | unsigned int searchlen = searchstr.length();
275 | int pos1 = -1, pos2 = -1;
276 | for(long i=0; i <= msglen - searchlen; i++) {
277 | if(tmsg.substring(i,searchlen+i) == searchstr) {
278 | pos1 = i + searchlen;
279 | break;
280 | }
281 | }
282 | searchstr = "\",\"";
283 | searchlen = searchstr.length();
284 | for(long i=pos1; i <= msglen - searchlen; i++) {
285 | if(tmsg.substring(i,searchlen+i) == searchstr) {
286 | pos2 = i;
287 | break;
288 | }
289 | }
290 | String text = tmsg.substring(pos1, pos2);
291 |
292 | searchstr = ",\"screen_name\":\"";
293 | searchlen = searchstr.length();
294 | int pos3 = -1, pos4 = -1;
295 | for(long i=pos2; i <= msglen - searchlen; i++) {
296 | if(tmsg.substring(i,searchlen+i) == searchstr) {
297 | pos3 = i + searchlen;
298 | break;
299 | }
300 | }
301 | searchstr = "\",\"";
302 | searchlen = searchstr.length();
303 | for(long i=pos3; i <= msglen - searchlen; i++) {
304 | if(tmsg.substring(i,searchlen+i) == searchstr) {
305 | pos4 = i;
306 | break;
307 | }
308 | }
309 | String usert = "@" + tmsg.substring(pos3, pos4);
310 |
311 | if (text.length() > 0) {
312 | if (usert.length() > 1) text = usert + " says " + text;
313 | text=convertUnicode(text);
314 | search_msg = std::string(text.c_str(), text.length());
315 | }
316 | }
317 |
318 | void updateDisplay(){
319 | #ifdef MAX7219DISPLAY
320 | char *msg = new char[search_msg.length() + 1];
321 | strcpy(msg, search_msg.c_str());
322 | display.sendSmooth (msg, messageOffset);
323 |
324 | // next time show one pixel onwards
325 | if (messageOffset++ >= (int) (strlen (msg) * 8)){
326 | messageOffset = - chips * 8;
327 | #endif
328 | if (twit_update) {
329 | digitalWrite(LED_BUILTIN, LOW);
330 | #ifdef MAX7219DISPLAY
331 | display.sendString ("--------");
332 | #endif
333 | #ifdef MD_PAROLA_DISPLAY
334 | P.displayClear();
335 | P.print("--------");
336 | #endif
337 | extractTweetText(tcr.searchTwitter(search_str));
338 | // extractJSON(tcr.searchTwitter(search_str)); // ArduinoJSON crashes esp8266, twitter info is too long
339 | DEBUG_PRINT("Search: ");
340 | DEBUG_PRINTLN(search_str.c_str());
341 | DEBUG_PRINT("MSG: ");
342 | DEBUG_PRINTLN(search_msg.c_str());
343 | #ifdef MD_PAROLA_DISPLAY
344 | strcpy(curmsg,search_msg.c_str());
345 | P.displayClear();
346 | P.displayText(curmsg,PA_LEFT,25,1000,PA_SCROLL_LEFT,PA_SCROLL_LEFT);
347 | #endif
348 | twit_update = false;
349 | }
350 | #ifdef MAX7219DISPLAY
351 | }
352 | delete [] msg;
353 | //free(msg);
354 | #endif
355 | } // end of updateDisplay
356 |
357 | void handleRoot() {
358 | String t;
359 | t += "";
360 | t += "";
361 | t += "";
362 | t += "";
363 | t += "";
364 | t += "";
365 | t += "";
366 | t += "Post to Twitter";
367 | t += "
";
368 | t += "";
369 | t += "";
373 | t += "";
374 | t += "";
375 | t += "";
376 | server.send(200, "text/html", t);
377 | }
378 |
379 | void getSearchWord() {
380 | String webpage;
381 | webpage = "";
382 | webpage += "";
383 | webpage += "Twitter IOT Scrolling Text Display";
384 | webpage += "";
387 | webpage += "";
388 | webpage += "";
389 | webpage += "
";
390 | webpage += "";
395 | webpage += "";
396 | webpage += "";
397 | server.send(200, "text/html", webpage); // Send a response to the client asking for input
398 | }
399 |
400 | void handleTweet() {
401 | if (server.method() == HTTP_POST) {
402 | std::string text;
403 | bool submit = false;
404 | for (uint8_t i=0; i";
422 | t += "";
423 | t += "";
424 | t += "";
425 | t += "";
426 | t += "";
427 | t += "Searching Twitter for: " + String(search_str.c_str()) + "
";
428 | t += "Latest Message: " + String(search_msg.c_str()) + "
";
429 | t += "
Update Search Term?";
430 | t += "";
431 | t += "";
432 | t += "";
433 | server.send(200, "text/html", t);
434 | }
435 |
436 | void processReadTweet(){
437 | if (server.args() > 0 and server.method() == HTTP_POST) { // Arguments were received
438 | for ( uint8_t i = 0; i < server.args(); i++ ) {
439 | DEBUG_PRINT(server.argName(i)); // Display the argument
440 | if (server.argName(i) == "search_input") {
441 | DEBUG_PRINT(" : ");
442 | DEBUG_PRINTLN(server.arg(i));
443 | search_str=std::string(server.arg(i).c_str());
444 | if(writetoFS(true)) DEBUG_PRINTLN(". done writing!"); // FS save
445 | }
446 | }
447 | }
448 |
449 | String t;
450 | t += "";
451 | t += "";
452 | t += "";
453 | t += "";
454 | t += "";
455 | t += "";
456 | t += "";
457 | t += "Updated search term: " + String(search_str.c_str()) + "
";
458 | t += "
Update again?";
459 | t += "";
460 | t += "";
461 | t += "";
462 | server.send(200, "text/html", t);
463 | }
464 |
465 | void handleNotFound(){
466 | String message = "File Not Found\n\n";
467 | message += "URI: ";
468 | message += server.uri();
469 | message += "\nMethod: ";
470 | message += (server.method() == HTTP_GET)?"GET":"POST";
471 | message += "\nArguments: ";
472 | message += server.args();
473 | message += "\n";
474 | for (uint8_t i=0; i api_lasttime + api_mtbs) and !(updateFS)) {
574 | twit_update = true;
575 | #ifndef MAX7219DISPLAY
576 | updateDisplay();
577 | #endif
578 | api_lasttime = millis();
579 | }
580 | #ifdef MAX7219DISPLAY
581 | if (millis() - lastMoved >= MOVE_INTERVAL){
582 | updateDisplay();
583 | lastMoved = millis();
584 | }
585 | #endif
586 | #ifdef MD_PAROLA_DISPLAY
587 | if (P.displayAnimate()) {
588 | updateDisplay();
589 | P.displayReset();
590 | }
591 | #endif
592 | yield();
593 | delay(2); //do something or else esp8266 is not happy, can remove this line if doing something else
594 | digitalWrite(LED_BUILTIN, HIGH);
595 | }
596 |
--------------------------------------------------------------------------------
/examples/esp8266/TwitterTweetSearchFSWiFiMgr/secret.h:
--------------------------------------------------------------------------------
1 | //WiFiManager AP Password
2 | #define AutoAP_password "wifiappassword" //password for WiFiManager AP
3 |
4 | //OTA
5 | #define ota_location "/firmware" // OTA update location
6 | #define ota_user "ota_admin" // OTA username
7 | #define ota_password "ota_pwd" // OTA password
8 |
9 | //Display
10 | //#define MAX7219DISPLAY // uncomment if using MAX7219-4-digit-display-for-ESP8266
11 | #define MD_PAROLA_DISPLAY // uncomment to use MD Parola Library
12 |
13 | // Twitter info
14 | #define TWITTERINFO
15 | // Values below are just a placeholder
16 | #ifdef TWITTERINFO // Obtain these by creating an app @ https://apps.twitter.com/
17 | static char const consumer_key[] = "gkyjeH3EF32NJfiuheuyf8623";
18 | static char const consumer_sec[] = "HbY5h$N86hg5jjd987HGFsRjJcMkjLaJw44628sOh353gI3H23";
19 | static char const accesstoken[] = "041657084136508135-F3BE63U4Y6b346kj6bnkdlvnjbGsd3V";
20 | static char const accesstoken_sec[] = "bsekjH8YT3dCWDdsgsdHUgdBiosesDgv43rknU4YY56Tj";
21 | #endif
--------------------------------------------------------------------------------
/keywords.txt:
--------------------------------------------------------------------------------
1 | #######################################
2 | # Syntax Coloring Map For TwitterWebAPI
3 | #######################################
4 |
5 | #######################################
6 | # Datatypes (KEYWORD1)
7 | #######################################
8 | TwitterClient KEYWORD1
9 | TwitterWebAPI KEYWORD1
10 |
11 | #######################################
12 | # Methods and Functions (KEYWORD2)
13 | #######################################
14 | tweet KEYWORD2
15 | searchTwitter KEYWORD2
16 | searchUser KEYWORD2
17 |
18 | #######################################
19 | # Constants (LITERAL1)
20 | #######################################
21 |
22 | consumer_key LITERAL1
23 | consumer_sec LITERAL1
24 | accesstoken LITERAL1
25 | accesstoken_sec LITERAL1
26 |
--------------------------------------------------------------------------------
/library.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TwitterWebAPI",
3 | "keywords": "twitter",
4 | "description": "Library for talking to Twitter on ESP8266.",
5 | "repository":
6 | {
7 | "type": "git",
8 | "url": "https://github.com/debsahu/TwitterWebAPI.git"
9 | },
10 | "frameworks": "arduino",
11 | "platforms": "espressif8266",
12 | "version": "0.1"
13 | }
14 |
--------------------------------------------------------------------------------
/library.properties:
--------------------------------------------------------------------------------
1 | name=TwitterWebAPI
2 | version=0.1
3 | author=debsahu
4 | maintainer=debsahu
5 | sentence=Twitter Web API
6 | paragraph=Library for talking to Twitter on ESP8266.
7 | category=Communication
8 | url=https://github.com/debsahu/TwitterWebAPI.git
9 | architectures=esp8266
10 |
--------------------------------------------------------------------------------