├── .gitignore └── ESP8266Tweet.ino /.gitignore: -------------------------------------------------------------------------------- 1 | soramimi_jp_bot.txt 2 | -------------------------------------------------------------------------------- /ESP8266Tweet.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | // settings 15 | 16 | const char *ssid = "SSID"; 17 | const char *password = "PASSWORD"; 18 | 19 | const char *HOSTNAME = "esp8266"; // example for http://esp8266.local/ 20 | 21 | static char const consumer_key[] = "xxxxxxxxxxxxxxxxxxxxxx"; 22 | static char const consumer_sec[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 23 | static char const accesstoken[] = "0000000000-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 24 | static char const accesstoken_sec[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 25 | 26 | const char *ntp_server = "time1.google.com"; 27 | const int TIMEZONE = 9 * 60 * 60; 28 | 29 | // 30 | 31 | time_t timevalue = 0; 32 | 33 | class misc { 34 | private: 35 | static void url_encode_(const char *ptr, const char *end, std::vector *out) 36 | { 37 | while (ptr < end) { 38 | int c = (unsigned char)*ptr; 39 | ptr++; 40 | if (isalnum(c) || strchr("_.-~", c)) { 41 | print(out, c); 42 | // } else if (c == ' ') { // 空白も16進エンコードするので、ここはコメント 43 | // print(out, '+'); 44 | } else { 45 | char tmp[10]; 46 | sprintf(tmp, "%%%02X", c); 47 | print(out, tmp[0]); 48 | print(out, tmp[1]); 49 | print(out, tmp[2]); 50 | } 51 | } 52 | } 53 | static void url_decode_(const char *ptr, const char *end, std::vector *out) 54 | { 55 | while (ptr < end) { 56 | int c = (unsigned char)*ptr; 57 | ptr++; 58 | if (c == '+') { 59 | c = ' '; 60 | } else if (c == '%' && isxdigit((unsigned char)ptr[0]) && isxdigit((unsigned char)ptr[1])) { 61 | char tmp[3]; // '%XX' 62 | tmp[0] = ptr[0]; 63 | tmp[1] = ptr[1]; 64 | tmp[2] = 0; 65 | c = strtol(tmp, NULL, 16); 66 | ptr += 2; 67 | } 68 | print(out, c); 69 | } 70 | } 71 | public: 72 | static void print(std::vector *out, char c) 73 | { 74 | out->push_back(c); 75 | } 76 | static void print(std::vector *out, char const *begin, char const *end) 77 | { 78 | out->insert(out->end(), begin, end); 79 | } 80 | static void print(std::vector *out, char const *ptr, size_t len) 81 | { 82 | print(out, ptr, ptr + len); 83 | } 84 | static void print(std::vector *out, char const *s) 85 | { 86 | print(out, s, s + strlen(s)); 87 | } 88 | static void print(std::vector *out, std::string const &s) 89 | { 90 | print(out, s.c_str(), s.size()); 91 | } 92 | static std::string to_stdstr(std::vector const &vec) 93 | { 94 | if (!vec.empty()) { 95 | char const *begin = &vec.at(0); 96 | char const *end = begin + vec.size(); 97 | return std::string(begin, end); 98 | } 99 | return std::string(); 100 | } 101 | 102 | static std::string url_encode(char const *str, char const *end) 103 | { 104 | if (!str) { 105 | return std::string(); 106 | } 107 | 108 | std::vector out; 109 | out.reserve(end - str + 10); 110 | 111 | url_encode_(str, end, &out); 112 | 113 | return to_stdstr(out); 114 | } 115 | static std::string url_decode(char const *str, char const *end) 116 | { 117 | if (!str) { 118 | return std::string(); 119 | } 120 | 121 | std::vector out; 122 | out.reserve(end - str + 10); 123 | 124 | url_decode_(str, end, &out); 125 | 126 | return to_stdstr(out); 127 | } 128 | 129 | static std::string url_encode(char const *str, size_t len) 130 | { 131 | return url_encode(str, str + len); 132 | } 133 | static std::string url_decode(char const *str, size_t len) 134 | { 135 | return url_decode(str, str + len); 136 | } 137 | 138 | static std::string url_encode(char const *str) 139 | { 140 | return url_encode(str, strlen(str)); 141 | } 142 | static std::string url_decode(const char *str) 143 | { 144 | return url_decode(str, strlen(str)); 145 | } 146 | 147 | static std::string url_encode(std::string const &str) 148 | { 149 | char const *begin = str.c_str(); 150 | char const *end = begin + str.size(); 151 | char const *ptr = begin; 152 | 153 | while (ptr < end) { 154 | int c = (unsigned char)*ptr; 155 | if (isalnum(c) || strchr("_.-~", c)) { 156 | // thru 157 | } else { 158 | break; 159 | } 160 | ptr++; 161 | } 162 | if (ptr == end) { 163 | return str; 164 | } 165 | 166 | std::vector out; 167 | out.reserve(str.size() + 10); 168 | 169 | out.insert(out.end(), begin, ptr); 170 | url_encode_(ptr, end, &out); 171 | 172 | return to_stdstr(out); 173 | } 174 | static std::string url_decode(std::string const &str) 175 | { 176 | char const *begin = str.c_str(); 177 | char const *end = begin + str.size(); 178 | char const *ptr = begin; 179 | 180 | while (ptr < end) { 181 | int c = *ptr & 0xff; 182 | if (c == '+' || c == '%') { 183 | break; 184 | } 185 | ptr++; 186 | } 187 | if (ptr == end) { 188 | return str; 189 | } 190 | 191 | 192 | std::vector out; 193 | out.reserve(str.size() + 10); 194 | 195 | out.insert(out.end(), begin, ptr); 196 | url_decode_(ptr, end, &out); 197 | 198 | return to_stdstr(out); 199 | } 200 | 201 | }; 202 | 203 | class base64 { 204 | private: 205 | static const unsigned char PAD = '='; 206 | static unsigned char enc(int c) 207 | { 208 | static const unsigned char _encode_table[] = { 209 | 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 210 | 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 211 | 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 212 | 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f, 213 | }; 214 | return _encode_table[c & 63]; 215 | } 216 | static unsigned char dec(int c) 217 | { 218 | static const unsigned char _decode_table[] = { 219 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 220 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 221 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, 222 | 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 223 | 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 224 | 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 225 | 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 226 | 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, 227 | }; 228 | return _decode_table[c & 127]; 229 | } 230 | public: 231 | static void encode(char const *src, size_t length, std::vector *out) 232 | { 233 | size_t srcpos, dstlen, dstpos; 234 | 235 | dstlen = (length + 2) / 3 * 4; 236 | out->resize(dstlen); 237 | if (dstlen == 0) { 238 | return; 239 | } 240 | char *dst = &out->at(0); 241 | dstpos = 0; 242 | for (srcpos = 0; srcpos < length; srcpos += 3) { 243 | int v = (unsigned char)src[srcpos] << 16; 244 | if (srcpos + 1 < length) { 245 | v |= (unsigned char)src[srcpos + 1] << 8; 246 | if (srcpos + 2 < length) { 247 | v |= (unsigned char)src[srcpos + 2]; 248 | dst[dstpos + 3] = enc(v); 249 | } else { 250 | dst[dstpos + 3] = PAD; 251 | } 252 | dst[dstpos + 2] = enc(v >> 6); 253 | } else { 254 | dst[dstpos + 2] = PAD; 255 | dst[dstpos + 3] = PAD; 256 | } 257 | dst[dstpos + 1] = enc(v >> 12); 258 | dst[dstpos] = enc(v >> 18); 259 | dstpos += 4; 260 | } 261 | } 262 | static void decode(char const *src, size_t length, std::vector *out) 263 | { 264 | unsigned char const *begin = (unsigned char const *)src; 265 | unsigned char const *end = begin + length; 266 | unsigned char const *ptr = begin; 267 | out->clear(); 268 | out->reserve(length * 3 / 4); 269 | int count = 0; 270 | int bits = 0; 271 | while (1) { 272 | if (isspace(*ptr)) { 273 | ptr++; 274 | } else { 275 | unsigned char c = 0xff; 276 | if (ptr < end && *ptr < 0x80) { 277 | c = dec(*ptr); 278 | } 279 | if (c < 0x40) { 280 | bits = (bits << 6) | c; 281 | count++; 282 | } else { 283 | if (count < 4) { 284 | bits <<= (4 - count) * 6; 285 | } 286 | c = 0xff; 287 | } 288 | if (count == 4 || c == 0xff) { 289 | if (count >= 2) { 290 | out->push_back(bits >> 16); 291 | if (count >= 3) { 292 | out->push_back(bits >> 8); 293 | if (count == 4) { 294 | out->push_back(bits); 295 | } 296 | } 297 | } 298 | count = 0; 299 | bits = 0; 300 | if (c == 0xff) { 301 | break; 302 | } 303 | } 304 | ptr++; 305 | } 306 | } 307 | } 308 | static void encode(std::vector const *src, std::vector *out) 309 | { 310 | encode(&src->at(0), src->size(), out); 311 | } 312 | static void decode(std::vector const *src, std::vector *out) 313 | { 314 | decode(&src->at(0), src->size(), out); 315 | } 316 | static void encode(char const *src, std::vector *out) 317 | { 318 | encode((char const *)src, strlen(src), out); 319 | } 320 | static void decode(char const *src, std::vector *out) 321 | { 322 | decode((char const *)src, strlen(src), out); 323 | } 324 | static inline std::string to_s_(std::vector const *vec) 325 | { 326 | if (!vec || vec->empty()) return std::string(); 327 | return std::string((char const *)&(*vec)[0], vec->size()); 328 | } 329 | static inline std::string encode(std::string const &src) 330 | { 331 | std::vector vec; 332 | encode((char const *)src.c_str(), src.size(), &vec); 333 | return to_s_(&vec); 334 | } 335 | static inline std::string decode(std::string const &src) 336 | { 337 | std::vector vec; 338 | decode((char const *)src.c_str(), src.size(), &vec); 339 | return to_s_(&vec); 340 | } 341 | 342 | }; 343 | 344 | class sha1 { 345 | public: 346 | enum { 347 | Success = 0, 348 | Null, /* Null pointer parameter */ 349 | InputTooLong, /* input data too long */ 350 | StateError /* called Input after Result */ 351 | }; 352 | public: 353 | static const int HashSize = 20; 354 | struct Context { 355 | uint32_t Intermediate_Hash[HashSize/4]; /* Message Digest */ 356 | 357 | uint32_t Length_Low; /* Message length in bits */ 358 | uint32_t Length_High; /* Message length in bits */ 359 | 360 | /* Index into message block array */ 361 | int_least16_t Message_Block_Index; 362 | uint8_t Message_Block[64]; /* 512-bit message blocks */ 363 | 364 | int Computed; /* Is the digest computed? */ 365 | int Corrupted; /* Is the message digest corrupted? */ 366 | }; 367 | static uint32_t CircularShift(uint32_t bits, uint32_t word) 368 | { 369 | return ((word) << (bits)) | ((word) >> (32-(bits))); 370 | } 371 | static void PadMessage(Context *context) 372 | { 373 | if (context->Message_Block_Index > 55) { 374 | context->Message_Block[context->Message_Block_Index++] = 0x80; 375 | while (context->Message_Block_Index < 64) { 376 | context->Message_Block[context->Message_Block_Index++] = 0; 377 | } 378 | 379 | ProcessMessageBlock(context); 380 | 381 | while (context->Message_Block_Index < 56) { 382 | context->Message_Block[context->Message_Block_Index++] = 0; 383 | } 384 | } else { 385 | context->Message_Block[context->Message_Block_Index++] = 0x80; 386 | while(context->Message_Block_Index < 56) { 387 | context->Message_Block[context->Message_Block_Index++] = 0; 388 | } 389 | } 390 | 391 | context->Message_Block[56] = context->Length_High >> 24; 392 | context->Message_Block[57] = context->Length_High >> 16; 393 | context->Message_Block[58] = context->Length_High >> 8; 394 | context->Message_Block[59] = context->Length_High; 395 | context->Message_Block[60] = context->Length_Low >> 24; 396 | context->Message_Block[61] = context->Length_Low >> 16; 397 | context->Message_Block[62] = context->Length_Low >> 8; 398 | context->Message_Block[63] = context->Length_Low; 399 | 400 | ProcessMessageBlock(context); 401 | } 402 | static void ProcessMessageBlock(Context *context) 403 | { 404 | const uint32_t K[] = { 405 | 0x5A827999, 406 | 0x6ED9EBA1, 407 | 0x8F1BBCDC, 408 | 0xCA62C1D6 409 | }; 410 | int t; 411 | uint32_t temp; 412 | uint32_t W[80]; 413 | uint32_t A, B, C, D, E; 414 | 415 | for (t = 0; t < 16; t++) { 416 | W[t] = context->Message_Block[t * 4] << 24; 417 | W[t] |= context->Message_Block[t * 4 + 1] << 16; 418 | W[t] |= context->Message_Block[t * 4 + 2] << 8; 419 | W[t] |= context->Message_Block[t * 4 + 3]; 420 | } 421 | 422 | for (t = 16; t < 80; t++) { 423 | W[t] = CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); 424 | } 425 | 426 | A = context->Intermediate_Hash[0]; 427 | B = context->Intermediate_Hash[1]; 428 | C = context->Intermediate_Hash[2]; 429 | D = context->Intermediate_Hash[3]; 430 | E = context->Intermediate_Hash[4]; 431 | 432 | for (t = 0; t < 20; t++) { 433 | temp = CircularShift(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; 434 | E = D; 435 | D = C; 436 | C = CircularShift(30,B); 437 | B = A; 438 | A = temp; 439 | } 440 | 441 | for (t = 20; t < 40; t++) { 442 | temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; 443 | E = D; 444 | D = C; 445 | C = CircularShift(30,B); 446 | B = A; 447 | A = temp; 448 | } 449 | 450 | for (t = 40; t < 60; t++) { 451 | temp = CircularShift(5,A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; 452 | E = D; 453 | D = C; 454 | C = CircularShift(30,B); 455 | B = A; 456 | A = temp; 457 | } 458 | 459 | for (t = 60; t < 80; t++) { 460 | temp = CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; 461 | E = D; 462 | D = C; 463 | C = CircularShift(30,B); 464 | B = A; 465 | A = temp; 466 | } 467 | 468 | context->Intermediate_Hash[0] += A; 469 | context->Intermediate_Hash[1] += B; 470 | context->Intermediate_Hash[2] += C; 471 | context->Intermediate_Hash[3] += D; 472 | context->Intermediate_Hash[4] += E; 473 | 474 | context->Message_Block_Index = 0; 475 | } 476 | public: 477 | static int Reset(Context *context) 478 | { 479 | if (!context) 480 | { 481 | return Null; 482 | } 483 | 484 | context->Length_Low = 0; 485 | context->Length_High = 0; 486 | context->Message_Block_Index = 0; 487 | 488 | context->Intermediate_Hash[0] = 0x67452301; 489 | context->Intermediate_Hash[1] = 0xEFCDAB89; 490 | context->Intermediate_Hash[2] = 0x98BADCFE; 491 | context->Intermediate_Hash[3] = 0x10325476; 492 | context->Intermediate_Hash[4] = 0xC3D2E1F0; 493 | 494 | context->Computed = 0; 495 | context->Corrupted = 0; 496 | 497 | return Success; 498 | } 499 | static int Result(Context *context, uint8_t Message_Digest[]) 500 | { 501 | int i; 502 | 503 | if (!context || !Message_Digest) { 504 | return Null; 505 | } 506 | 507 | if (context->Corrupted) { 508 | return context->Corrupted; 509 | } 510 | 511 | if (!context->Computed) { 512 | PadMessage(context); 513 | for (i = 0; i < 64; ++i) { 514 | /* message may be sensitive, clear it out */ 515 | context->Message_Block[i] = 0; 516 | } 517 | context->Length_Low = 0; /* and clear length */ 518 | context->Length_High = 0; 519 | context->Computed = 1; 520 | } 521 | 522 | for (i = 0; i < HashSize; ++i) 523 | { 524 | Message_Digest[i] = context->Intermediate_Hash[i>>2] >> 8 * ( 3 - ( i & 0x03 ) ); 525 | } 526 | 527 | return Success; 528 | } 529 | static int Input(Context *context, const uint8_t *message_array, unsigned int length) 530 | { 531 | if (!length) { 532 | return Success; 533 | } 534 | 535 | if (!context || !message_array) { 536 | return Null; 537 | } 538 | 539 | if (context->Computed) { 540 | context->Corrupted = StateError; 541 | return StateError; 542 | } 543 | 544 | if (context->Corrupted) { 545 | return context->Corrupted; 546 | } 547 | while(length-- && !context->Corrupted) { 548 | context->Message_Block[context->Message_Block_Index++] = (*message_array & 0xFF); 549 | 550 | context->Length_Low += 8; 551 | if (context->Length_Low == 0) { 552 | context->Length_High++; 553 | if (context->Length_High == 0) { 554 | /* Message is too long */ 555 | context->Corrupted = 1; 556 | } 557 | } 558 | 559 | if (context->Message_Block_Index == 64) { 560 | ProcessMessageBlock(context); 561 | } 562 | 563 | message_array++; 564 | } 565 | 566 | return Success; 567 | } 568 | }; 569 | 570 | class oauth { 571 | public: 572 | class Keys { 573 | public: 574 | std::string consumer_key; 575 | std::string consumer_sec; 576 | std::string accesstoken; 577 | std::string accesstoken_sec; 578 | 579 | Keys() 580 | { 581 | } 582 | 583 | Keys(std::string consumer_key, std::string consumer_sec, std::string accesstoken, std::string accesstoken_sec) 584 | : consumer_key(consumer_key) 585 | , consumer_sec(consumer_sec) 586 | , accesstoken(accesstoken) 587 | , accesstoken_sec(accesstoken_sec) 588 | { 589 | } 590 | }; 591 | enum http_method_t { 592 | GET, 593 | POST, 594 | }; 595 | struct Request { 596 | std::string url; 597 | std::string post; 598 | }; 599 | static Request sign(const char *url, http_method_t http_method, Keys const &keys) 600 | { 601 | std::vector vec; 602 | split_url(url, &vec); 603 | 604 | process_(&vec, http_method, keys); 605 | 606 | if (http_method == POST) { 607 | Request req; 608 | req.post = oauth::build_url(vec, 1); 609 | req.url = vec.at(0); 610 | return req; 611 | } else { 612 | Request req; 613 | req.url = oauth::build_url(vec, 0); 614 | return req; 615 | } 616 | } 617 | private: 618 | static std::string encode_base64(const unsigned char *src, int size) 619 | { 620 | std::vector vec; 621 | base64::encode((char const *)src, size, &vec); 622 | return misc::to_stdstr(vec); 623 | 624 | } 625 | static std::string decode_base64(const char *src) 626 | { 627 | std::vector vec; 628 | base64::decode(src, &vec); 629 | return misc::to_stdstr(vec); 630 | } 631 | static std::string url_escape(const char *string) 632 | { 633 | return misc::url_encode(string); 634 | } 635 | static std::string url_unescape(const char *string) 636 | { 637 | return misc::url_decode(string); 638 | } 639 | static void process_(std::vector *args, http_method_t http_method, const Keys &keys) 640 | { 641 | auto to_s = [](int v)->std::string{ 642 | char tmp[100]; 643 | sprintf(tmp, "%d", v); 644 | return tmp; 645 | }; 646 | 647 | { 648 | auto nonce = [](){ 649 | static const char *chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; 650 | const unsigned int max = 26 + 26 + 10 + 1; 651 | char tmp[50]; 652 | // srand((unsigned int)time(0)); 653 | srand((unsigned int)timevalue); 654 | int len = 15 + rand() % 16; 655 | for (int i = 0; i < len; i++) { 656 | tmp[i] = chars[rand() % max]; 657 | } 658 | return std::string(tmp, len); 659 | }; 660 | 661 | auto is_key_contains = [](std::vector const &argv, std::string const &key) 662 | { 663 | size_t keylen = key.size(); 664 | for (std::string const &s : argv) { 665 | if (strncmp(s.c_str(), key.c_str(), keylen) == 0 && s[keylen] == '=') { 666 | return true; 667 | } 668 | } 669 | return false; 670 | }; 671 | 672 | std::string oauth_nonce = "oauth_nonce"; 673 | if (!is_key_contains(*args, oauth_nonce)) { 674 | oauth_nonce += '='; 675 | oauth_nonce += nonce(); 676 | args->push_back(oauth_nonce); 677 | } 678 | 679 | std::string oauth_timestamp = "oauth_timestamp"; 680 | if (!is_key_contains(*args, oauth_timestamp)) { 681 | oauth_timestamp += '='; 682 | // oauth_timestamp += to_s((int)time(nullptr)); 683 | oauth_timestamp += to_s((int)timevalue); 684 | args->push_back(oauth_timestamp); 685 | } 686 | 687 | if (!keys.accesstoken.empty()) { 688 | std::string oauth_token = "oauth_token"; 689 | oauth_token += '='; 690 | oauth_token += keys.accesstoken; 691 | args->push_back(oauth_token); 692 | } 693 | 694 | std::string oauth_consumer_key = "oauth_consumer_key"; 695 | oauth_consumer_key += '='; 696 | oauth_consumer_key += keys.consumer_key; 697 | args->push_back(oauth_consumer_key); 698 | 699 | std::string oauth_signature_method = "oauth_signature_method"; 700 | oauth_signature_method += '='; 701 | oauth_signature_method += "HMAC-SHA1"; 702 | args->push_back(oauth_signature_method); 703 | 704 | std::string oauth_version = "oauth_version"; 705 | if (!is_key_contains(*args, oauth_version)) { 706 | oauth_version += '='; 707 | oauth_version += "1.0"; 708 | args->push_back(oauth_version); 709 | } 710 | 711 | } 712 | std::sort(args->begin() + 1, args->end()); 713 | 714 | auto Combine = [](std::initializer_list list){ 715 | std::string text; 716 | for (std::string const &s : list) { 717 | if (!s.empty()) { 718 | if (!text.empty()) { 719 | text += '&'; 720 | } 721 | text += misc::url_encode(s); 722 | } 723 | } 724 | return text; 725 | }; 726 | 727 | std::string query = oauth::build_url(*args, 1); 728 | 729 | std::string httpmethod; 730 | if (http_method == http_method_t::GET) { 731 | httpmethod = "GET"; 732 | } else if (http_method == http_method_t::POST) { 733 | httpmethod = "POST"; 734 | } 735 | std::string m = Combine({httpmethod, (*args)[0], query}); 736 | std::string k = Combine({std::string(keys.consumer_sec), std::string(keys.accesstoken_sec)}); 737 | 738 | std::string oauth_signature = "oauth_signature"; 739 | oauth_signature += '='; 740 | oauth_signature += sign_hmac_sha1(m, k); 741 | 742 | args->push_back(oauth_signature); 743 | } 744 | static void split_url(const char *url, std::vector *out) 745 | { 746 | out->clear(); 747 | char const *left = url; 748 | char const *ptr = left; 749 | while (1) { 750 | int c = *ptr & 0xff; 751 | if (c == '&' || c == '?' || c == 0) { 752 | if (left < ptr) { 753 | out->push_back(std::string(left, ptr)); 754 | } 755 | if (c == 0) break; 756 | ptr++; 757 | left = ptr; 758 | } else { 759 | ptr++; 760 | } 761 | } 762 | for (size_t i = 1; i < out->size(); i++) { 763 | std::string *p = &out->at(i); 764 | *p = misc::url_decode(*p); 765 | } 766 | } 767 | static std::string build_url(const std::vector &argv, int start) 768 | { 769 | const char sep = '&'; 770 | std::string query; 771 | for (size_t i = start; i < argv.size(); i++) { 772 | std::string s = argv[i]; 773 | if (i > 0) { 774 | char const *p = s.c_str(); 775 | char const *e = strchr(p, '='); 776 | if (e) { 777 | std::string name(p, e); 778 | std::string value = e + 1; 779 | s = name + '=' + misc::url_encode(value); 780 | } else { 781 | s += '='; 782 | } 783 | } 784 | if (!query.empty()) { 785 | query += sep; 786 | } 787 | query += s; 788 | } 789 | return query; 790 | } 791 | static void hmac_sha1(uint8_t const *key, size_t keylen, uint8_t const *in, size_t inlen, uint8_t *out) 792 | { 793 | sha1::Context sha1; 794 | uint8_t tmp[20]; 795 | 796 | uint8_t ibuf[64]; 797 | uint8_t obuf[64]; 798 | memset(ibuf, 0, 64); 799 | memset(obuf, 0, 64); 800 | memcpy(ibuf, key, keylen); 801 | memcpy(obuf, key, keylen); 802 | 803 | for (int i = 0; i < 64; i++) { 804 | ibuf[i] ^= 0x36; 805 | obuf[i] ^= 0x5c; 806 | } 807 | 808 | sha1::Reset(&sha1); 809 | sha1::Input(&sha1, ibuf, 64); 810 | sha1::Input(&sha1, in, inlen); 811 | sha1::Result(&sha1, tmp); 812 | 813 | sha1::Reset(&sha1); 814 | sha1::Input(&sha1, obuf, 64); 815 | sha1::Input(&sha1, tmp, 20); 816 | sha1::Result(&sha1, out); 817 | } 818 | static std::string sign_hmac_sha1(std::string const &m, std::string const &k) 819 | { 820 | uint8_t key[20]; 821 | uint8_t result[20]; 822 | 823 | sha1::Context sha1; 824 | sha1::Reset(&sha1); 825 | sha1::Input(&sha1, (uint8_t const *)k.c_str(), k.size()); 826 | sha1::Result(&sha1, key); 827 | 828 | hmac_sha1(key, 20, (uint8_t const *)m.c_str(), m.size(), result); 829 | std::vector vec; 830 | base64::encode((char const *)result, 20, &vec); 831 | return misc::to_stdstr(vec); 832 | } 833 | }; 834 | 835 | class URL { 836 | private: 837 | std::string scheme_; 838 | std::string host_; 839 | int port_ = 0; 840 | std::string path_; 841 | public: 842 | URL(const std::string &url) 843 | { 844 | char const *str = url.c_str(); 845 | char const *left; 846 | char const *right; 847 | left = str; 848 | right = strstr(left, "://"); 849 | if (right) { 850 | scheme_.assign(str, right - str); 851 | left = right + 3; 852 | } 853 | right = strchr(left, '/'); 854 | if (right) { 855 | char const *p = strchr(left, ':'); 856 | if (p && left < p && p < right) { 857 | int n = 0; 858 | char const *q = p + 1; 859 | while (q < right) { 860 | if (isdigit(*q & 0xff)) { 861 | n = n * 10 + (*q - '0'); 862 | } else { 863 | n = -1; 864 | break; 865 | } 866 | q++; 867 | } 868 | host_.assign(left, p - left); 869 | if (n > 0 && n < 65536) { 870 | port_ = n; 871 | } 872 | } else { 873 | host_.assign(left, right - left); 874 | } 875 | path_ = right; 876 | } 877 | if (port_ == 0) { 878 | if (scheme_ == "http") { 879 | port_ = 80; 880 | } else if (scheme_ == "https") { 881 | port_ = 443; 882 | } 883 | } 884 | } 885 | std::string const &scheme() const { return scheme_; } 886 | std::string const &host() const { return host_; } 887 | int port() const { return port_; } 888 | std::string const &path() const { return path_; } 889 | bool isssl() const 890 | { 891 | if (scheme() == "https") return true; 892 | if (scheme() == "http") return false; 893 | if (port() == 443) return true; 894 | return false; 895 | } 896 | }; 897 | 898 | class TwitterClient { 899 | private: 900 | struct Data { 901 | oauth::Keys keys; 902 | } data; 903 | oauth::Keys const &keys() const 904 | { 905 | return data.keys; 906 | } 907 | struct RequestOption { 908 | enum Method { 909 | GET, 910 | POST, 911 | }; 912 | Method method = GET; 913 | char const *post_begin = nullptr; 914 | char const *post_end = nullptr; 915 | std::string upload_path; 916 | void set_post_data(char const *begin, char const *end) 917 | { 918 | post_begin = begin; 919 | post_end = end; 920 | method = POST; 921 | } 922 | void set_upload_path(std::string const &path) 923 | { 924 | upload_path = path; 925 | } 926 | }; 927 | bool request(const std::string &url, RequestOption const &opt, std::string *reply) 928 | { 929 | if (reply) { 930 | reply->clear(); 931 | } 932 | if (opt.method == RequestOption::GET) { 933 | // not implemented 934 | } else if (opt.method == RequestOption::POST) { 935 | if (opt.upload_path.empty()) { 936 | String host, path; 937 | int port = 0; 938 | { 939 | URL l(url); 940 | host = l.host().c_str(); 941 | path = l.path().c_str(); 942 | port = l.port(); 943 | } 944 | WiFiClientSecure client; 945 | if (client.connect(host.c_str(), port)) { 946 | int len = opt.post_end - opt.post_begin; 947 | client.println("POST " + path + " HTTP/1.1"); 948 | client.println("Host: " + host); 949 | client.println("User-Agent: ESP8266"); 950 | client.println("Connection: close"); 951 | client.println("Content-Type: application/x-www-form-urlencoded;"); 952 | client.print("Content-Length: "); 953 | client.println(len); 954 | client.println(); 955 | client.write((uint8_t const *)opt.post_begin, len); 956 | // delay(1000); 957 | String s = client.readString(); 958 | *reply = std::string(s.c_str(), s.length()); 959 | // Serial.print(reply->c_str()); 960 | Serial.println("Tweeted"); 961 | return true; 962 | } 963 | } else { 964 | // not implemented 965 | } 966 | } 967 | return false; 968 | } 969 | public: 970 | TwitterClient() 971 | { 972 | } 973 | TwitterClient(std::string const &consumer_key, std::string const &consumer_sec, std::string const &accesstoken, std::string const &accesstoken_sec) 974 | { 975 | data.keys.consumer_key = consumer_key; 976 | data.keys.consumer_sec = consumer_sec; 977 | data.keys.accesstoken = accesstoken; 978 | data.keys.accesstoken_sec = accesstoken_sec; 979 | } 980 | bool tweet(std::string message, const std::vector *media_ids = nullptr) 981 | { 982 | if (message.empty()) { 983 | return false; 984 | } 985 | 986 | std::string url = "https://api.twitter.com/1.1/statuses/update.json"; 987 | 988 | url += "?status="; 989 | url += misc::url_encode(message); 990 | if (media_ids && !media_ids->empty()) { 991 | std::string ids; 992 | for (std::string const &media_id : *media_ids) { 993 | if (!media_id.empty()) { 994 | if (!ids.empty()) { 995 | ids += ','; 996 | } 997 | ids += media_id; 998 | } 999 | } 1000 | if (!ids.empty()) { 1001 | url += "&media_ids="; 1002 | url += ids; 1003 | } 1004 | } 1005 | 1006 | oauth::Request oauth_req = oauth::sign(url.c_str(), oauth::POST, keys()); 1007 | std::string res; 1008 | RequestOption opt; 1009 | char const *p = oauth_req.post.c_str(); 1010 | opt.set_post_data(p, p + oauth_req.post.size()); 1011 | return request(oauth_req.url, opt, &res); 1012 | } 1013 | }; 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | unsigned int ntp_local_port = 12300; 1020 | IPAddress ntp_server_addr; 1021 | const int NTP_PACKET_SIZE = 48; 1022 | WiFiUDP ntp_udp; 1023 | uint64_t millis64 = 0; 1024 | 1025 | void send_ntp_request() 1026 | { 1027 | byte buf[NTP_PACKET_SIZE]; 1028 | memset(buf, 0, NTP_PACKET_SIZE); 1029 | buf[0] = 0xdb; 1030 | ntp_udp.beginPacket(ntp_server_addr, 123); // NTP requests are to port 123 1031 | ntp_udp.write(buf, NTP_PACKET_SIZE); 1032 | ntp_udp.endPacket(); 1033 | } 1034 | 1035 | uint64_t recv_udp_packet() 1036 | { 1037 | int n = ntp_udp.parsePacket(); 1038 | if (n < NTP_PACKET_SIZE) return 0; 1039 | byte buf[NTP_PACKET_SIZE]; 1040 | ntp_udp.read(buf, NTP_PACKET_SIZE); // read the packet into the buffer 1041 | uint32_t s = (((unsigned long)buf[0x28] << 24) | ((unsigned long)buf[0x29] << 16) | ((unsigned long)buf[0x2a] << 8) | (unsigned long)buf[0x2b]); 1042 | uint32_t m = (((unsigned long)buf[0x2c] << 24) | ((unsigned long)buf[0x2d] << 16) | ((unsigned long)buf[0x2e] << 8) | (unsigned long)buf[0x2f]); 1043 | m = ((m >> 16) * 1000 + 32768) >> 16; 1044 | return (uint64_t)s * 1000 + m; 1045 | } 1046 | 1047 | 1048 | ESP8266WebServer server(80); 1049 | 1050 | void handleRoot() { 1051 | String t; 1052 | t += ""; 1053 | t += ""; 1054 | t += ""; 1055 | t += ""; 1056 | t += ""; 1057 | t += "

"; 1058 | t += "ESP8266 Twitter bot test"; 1059 | t += "

"; 1060 | t += "
"; 1061 | t += ""; 1062 | t += ""; 1063 | t += "
"; 1064 | t += ""; 1065 | t += ""; 1066 | server.send(200, "text/html", t); 1067 | } 1068 | 1069 | void handleTweet() { 1070 | if (server.method() == HTTP_POST) { 1071 | std::string text; 1072 | bool submit = false; 1073 | for (uint8_t i=0; i 0) { 1178 | s -= (uint64_t)25567 * 24 * 60 * 60; // shift 70yrs (to unix time) 1179 | timevalue = (time_t)s; 1180 | } 1181 | timeout--; 1182 | } 1183 | } 1184 | } 1185 | 1186 | void loop(void){ 1187 | processTime(); 1188 | server.handleClient(); 1189 | } 1190 | 1191 | --------------------------------------------------------------------------------