shinji

Old Twitter code for ZNC (Outdated)

Jul 28th, 2016
225
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 62.24 KB | None | 0 0
  1. /*
  2.  * Copyright (C) 2009-2012 flakes @ EFNet
  3.  * Updated 18 October 2012
  4.  *
  5.  * This program is free software; you can redistribute it and/or modify it
  6.  * under the terms of the GNU General Public License version 2 as published
  7.  * by the Free Software Foundation.
  8.  */
  9.  
  10. #define REQUIRESSL
  11.  
  12. #include "main.h"
  13. #include "znc.h"
  14. #include "Chan.h"
  15. #include "Modules.h"
  16. #include "User.h"
  17.  
  18. #if (!defined(VERSION_MAJOR) || !defined(VERSION_MINOR) || (VERSION_MAJOR == 0 && VERSION_MINOR < 72))
  19. #error This module needs ZNC 0.072 or newer.
  20. #endif
  21.  
  22. #include <stack>
  23. #include <deque>
  24. #include <exception>
  25. #include <algorithm>
  26. #include <limits>
  27.  
  28. #include <openssl/evp.h>
  29. #include <openssl/hmac.h>
  30.  
  31. using namespace std;
  32.  
  33. /************************************************************************/
  34. /* APP IDENTIFICATION STUFF PROVIDED BY TWITTER                         */
  35. /************************************************************************/
  36.  
  37. #define TWITTER_CONSUMER_KEY "9k8Qvy4wncJzZmOKeJAQ"
  38. #define TWITTER_CONSUMER_SECRET "foW4mvWuFKy3iD9r4cHdekZ4h9P0qiq9jDBN0ZHj2U"
  39.  
  40. /************************************************************************/
  41. /* SOME NEEDED STRUCTS                                                  */
  42. /************************************************************************/
  43.  
  44. enum ETwitterFeedType
  45. {
  46.     TWFT_TIMELINE = 1,
  47.     TWFT_MENTIONS,
  48.     TWFT_USER,
  49.     TWFT_SEARCH,
  50.     _TWFT_MAX
  51. };
  52.  
  53. struct CTwitterFeed
  54. {
  55.     CTwitterFeed(ETwitterFeedType type) { m_type = type; m_lastUpdate = 0; m_lastId = 0; m_id = 0; m_colors = true; }
  56.     ETwitterFeedType m_type;
  57.     int m_id;
  58.     CString m_payload;
  59.     CString m_target;
  60.     CString m_prefix;
  61.     uint64_t m_lastId;
  62.     time_t m_lastUpdate;
  63.     bool m_colors;
  64. };
  65.  
  66. struct CFontStyle
  67. {
  68.     CFontStyle() { bUnderline = bBold = false; iForeClr = iBackClr = -1; }
  69.     bool bUnderline, bBold;
  70.     int iForeClr, iBackClr;
  71.  
  72.     CString GetStartCode() const
  73.     {
  74.         return CString(bBold ? "\x02" : "") +
  75.             CString(bUnderline ? "\x1F" : "") +
  76.             (iForeClr > -1 ? "\x03" + CString(iForeClr < 10 && iBackClr == -1 ? "0" : "") + CString(iForeClr)
  77.                 + (iBackClr > -1 ? "," + CString(iBackClr < 10 ? "0" : "") + CString(iBackClr) : "") : "");
  78.     }
  79.  
  80.     CString GetEndCode() const
  81.     {
  82.         return CString(bBold ? "\x02" : "") +
  83.             CString(bUnderline ? "\x1F" : "") +
  84.             CString(iForeClr > -1 ? "\x03" : "");
  85.     }
  86. };
  87.  
  88. /************************************************************************/
  89. /*  MODULE HEADER / DECLARATION                                         */
  90. /************************************************************************/
  91.  
  92. class CTRRequestToken;
  93.  
  94. class CTwitterModule : public CModule
  95. {
  96.     friend class CTRRequestToken;
  97. private:
  98.     CString m_token, m_tokenSecret;
  99.     bool m_waitingForPIN, m_hasAccessToken;
  100.     CString m_screenName;
  101.     uint64_t m_userId;
  102.     int m_nextFeedId;
  103.     vector<CTwitterFeed> m_feeds;
  104.     vector<pair<time_t, CString> > m_msgQueue;
  105.     time_t m_msgQueueLastSent;
  106.     time_t m_lastRateLimitedCall;
  107.     map<const CString, CFontStyle> m_styles;
  108.  
  109.     void SaveSettings();
  110.     void LoadSettings();
  111. public:
  112.     MODCONSTRUCTOR(CTwitterModule)
  113.     {
  114.         m_waitingForPIN = m_hasAccessToken = false;
  115.         m_userId = 0;
  116.         m_nextFeedId = 1;
  117.         m_msgQueueLastSent = m_lastRateLimitedCall = 0;
  118.         m_styles["user"] = CFontStyle();
  119.         m_styles["@user"] = CFontStyle();
  120.         m_styles["hashtag"] = CFontStyle();
  121.         m_styles["link"] = CFontStyle();
  122.         m_styles["timestamp"] = CFontStyle();
  123.         m_styles["text"] = CFontStyle();
  124.     }
  125.  
  126.     const CString& GetToken() { return m_token; }
  127.     const CString& GetTokenSecret() { return m_tokenSecret; }
  128.     const CString& GetScreenName() { return m_screenName; }
  129.     bool IsAuthed() { return m_hasAccessToken; }
  130.  
  131.     void EraseSession(bool bErrorMessage);
  132.     void SendTweet(CString sMessage);
  133.     void TimerAction();
  134.  
  135.     CTwitterFeed *GetFeedById(int id);
  136.     time_t InterpretRFC3339Time(const CString& sRFC3339);
  137.     static CString FormatTweetTime(time_t iTime, bool bAllowFuture = false);
  138.     void QueueMessage(CTwitterFeed *fFeed, time_t iTime, const CString& sMessage);
  139.  
  140.     bool OnLoad(const CString& sArgsi, CString& sMessage);
  141.     void OnModCommand(const CString& sCommand);
  142.     void OnModCTCP(const CString& sMessage);
  143.     ~CTwitterModule();
  144. };
  145.  
  146. /************************************************************************/
  147. /* MISC HELPER + WRAPPER FUNCTIONS                                      */
  148. /************************************************************************/
  149.  
  150. static CString HMACSHA1(const CString& sInput, const CString& sKey)
  151. {
  152.     HMAC_CTX ctx;
  153.     unsigned char hmacBuf[EVP_MAX_MD_SIZE];
  154.     unsigned int len = 0;
  155.  
  156.     HMAC_Init(&ctx, sKey.c_str(), sKey.size(), EVP_sha1());
  157.     HMAC_Update(&ctx, (const unsigned char*)sInput.c_str(), sInput.size());
  158.     HMAC_Final(&ctx, &hmacBuf[0], &len);
  159.  
  160.     HMAC_CTX_cleanup(&ctx);
  161.  
  162.     CString sTmp;
  163.     sTmp.append((char*)&hmacBuf, len);
  164.     sTmp.Base64Encode();
  165.  
  166.     return sTmp;
  167. }
  168.  
  169. /************************************************************************/
  170. /* HTTP CLIENT SOCKET                                                   */
  171. /************************************************************************/
  172.  
  173. class CSimpleHTTPSock : protected CSocket
  174. {
  175. public:
  176.     CSimpleHTTPSock(CModule *pModInstance)
  177.         : CSocket(pModInstance)
  178.     {
  179.         m_pMod = pModInstance;
  180.         m_maxRedirs = 5;
  181.         m_numRedir = 0;
  182.         m_maxBuf = 1024 * 1024;
  183.  
  184.         DisableReadLine();
  185.     }
  186.  
  187.     virtual ~CSimpleHTTPSock()
  188.     {
  189.     }
  190.  
  191.     static CString URLEscape(const CString& s)
  192.     {
  193.         return s.Escape_n(CString::EASCII, CString::EURL).Replace_n("+", "%20");
  194.     }
  195.  
  196.     static bool CrackURL(const CString& sURL, CString& srHost, unsigned short& urPort, CString& srPath, bool& brSSL)
  197.     {
  198.         CString sWork(sURL);
  199.  
  200.         if(sWork.substr(0, 7) == "http://")
  201.         {
  202.             brSSL = false;
  203.             urPort = 80;
  204.  
  205.             sWork.erase(0, 7);
  206.         }
  207.         else if(sURL.substr(0, 8) == "https://")
  208.         {
  209.             brSSL = true;
  210.             urPort = 443;
  211.  
  212.             sWork.erase(0, 8);
  213.         }
  214.         else
  215.             return false;
  216.  
  217.         CString::size_type uPos = sWork.find('/');
  218.  
  219.         if(uPos == CString::npos)
  220.         {
  221.             srHost = sWork;
  222.             srPath = "/";
  223.         }
  224.         else
  225.         {
  226.             srHost = sWork.substr(0, uPos);
  227.             srPath = sWork.substr(uPos + 1);
  228.         }
  229.  
  230.         uPos = srHost.find(':');
  231.  
  232.         if(uPos != CString::npos)
  233.         {
  234.             unsigned long long uTmp = CString(srHost.substr(uPos + 1)).ToULongLong();
  235.  
  236.             if(uTmp > 0 && uTmp <= numeric_limits<unsigned short>::max())
  237.             {
  238.                 urPort = (unsigned short)uTmp;
  239.                 srHost = srHost.substr(0, uPos);
  240.             }
  241.             else
  242.                 return false;
  243.         }
  244.  
  245.         // validate host + path:
  246.         if(srHost.empty()) return false;
  247.  
  248.         for(CString::size_type p = 0; p < srHost.size(); p++)
  249.         {
  250.             if(!isalnum(srHost[p]) && srHost[p] != '.' && srHost[p] != '-')
  251.             {
  252.                 return false;
  253.             }
  254.         }
  255.  
  256.         for(CString::size_type p = 0; p < srPath.size(); p++)
  257.         {
  258.             if(!srPath[p] || srPath[p] == '\n' || srPath[p] == '\r')
  259.             {
  260.                 return false;
  261.             }
  262.         }
  263.  
  264.         return true;
  265.     }
  266.  
  267. private:
  268.     CString m_request;
  269.     CString m_buffer;
  270.     unsigned int m_numRedir;
  271.  
  272. protected:
  273.     CModule *m_pMod;
  274.     unsigned int m_maxRedirs;
  275.     unsigned int m_maxBuf;
  276.     MCString m_extraReqHeaders;
  277.  
  278.     void MakeRequestHeaders(bool bPost, const CString& sHost, const CString& sPath, unsigned short uPort, bool bSSL)
  279.     {
  280.         m_request = CString(bPost ? "POST " : "GET ") + sPath + " HTTP/1.1\r\n";
  281.         m_request += "Host: " + sHost + ((uPort == 80 && !bSSL) || (uPort == 443 && bSSL) ? CString("") : ":" + CString(uPort)) + "\r\n";
  282.         m_request += "User-Agent: Mozilla/5.0 (" + CZNC::GetTag() + ")\r\n";
  283.  
  284.         for(MCString::const_iterator it = m_extraReqHeaders.begin(); it != m_extraReqHeaders.end(); it++)
  285.         {
  286.             m_request += it->first + ": " + it->second + "\r\n";
  287.         }
  288.  
  289.         m_request += "Connection: Close\r\n";
  290.     }
  291.  
  292.     void Get(const CString& sHost, const CString& sPath, unsigned short uPort = 80, bool bSSL = false)
  293.     {
  294.         MakeRequestHeaders(false, sHost, sPath, uPort, bSSL);
  295.         m_request += "\r\n";
  296.  
  297.         DEBUG("[Twitter] Connecting to [" << sHost << "]:" << uPort << " (SSL = " << bSSL << ")");
  298.         Connect(sHost, uPort, bSSL);
  299.     }
  300.  
  301.     void Post(const MCString& mPostData, const CString& sHost, const CString& sPath, unsigned short uPort = 80, bool bSSL = false)
  302.     {
  303.         MakeRequestHeaders(true, sHost, sPath, uPort, bSSL);
  304.  
  305.         CString sPostData;
  306.  
  307.         for(MCString::const_iterator it = mPostData.begin(); it != mPostData.end(); it++)
  308.         {
  309.             if(it != mPostData.begin()) sPostData += "&";
  310.             sPostData += URLEscape(it->first) + "=" + URLEscape(it->second);
  311.         }
  312.  
  313.         m_request += "Content-Type: application/x-www-form-urlencoded\r\n";
  314.         m_request += "Content-Length: " + CString(sPostData.size()) + "\r\n";
  315.         m_request += "\r\n";
  316.         m_request += sPostData;
  317.  
  318.         DEBUG("[Twitter] Connecting to [" << sHost << "]:" << uPort << " (SSL = " << bSSL << ")");
  319.         Connect(sHost, uPort, bSSL);
  320.     }
  321.  
  322.     void Connected()
  323.     {
  324.         m_buffer.clear();
  325.         DEBUG("[Twitter] Sending request: " << m_request);
  326.         Write(m_request);
  327.         m_request.clear();
  328.     }
  329.  
  330.     virtual void Timeout()
  331.     {
  332.         m_request.clear();
  333.         Close();
  334.     }
  335.  
  336.     void Disconnected()
  337.     {
  338.         unsigned int uResponseCode;
  339.  
  340.         if(sscanf(m_buffer.c_str(), "HTTP/1.%*c %u ", &uResponseCode) == 1)
  341.         {
  342.             CString::size_type uPos = m_buffer.find("\r\n\r\n");
  343.             if(uPos == CString::npos) uPos = m_buffer.find("\n\n");
  344.  
  345.             DEBUG("[Twitter] Response: " << m_buffer);
  346.  
  347.             if(uPos != CString::npos)
  348.             {
  349.                 VCString vHeadersTemp;
  350.                 map<const CString, CString> mHeaders;
  351.  
  352.                 CString(m_buffer.substr(0, uPos)).Split("\n", vHeadersTemp, false, "", "", false, true);
  353.  
  354.                 for(VCString::const_iterator it = vHeadersTemp.begin(); it != vHeadersTemp.end(); it++)
  355.                 {
  356.                     CString sVal = it->Token(1, true, ":");
  357.                     sVal.Trim();
  358.  
  359.                     mHeaders[it->Token(0, false, ":")] = sVal;
  360.                 }
  361.  
  362.                 if(uResponseCode >= 300 && uResponseCode < 400 && mHeaders.find("Location") != mHeaders.end() && m_numRedir < m_maxRedirs)
  363.                 {
  364.                     bool bSSL; unsigned short uPort;
  365.                     CString sHost, sPath;
  366.  
  367.                     if(CrackURL(mHeaders["Location"], sHost, uPort, sPath, bSSL))
  368.                     {
  369.                         m_numRedir++;
  370.                         Get(sHost, sPath, uPort, bSSL);
  371.                     }
  372.                     else
  373.                         OnRequestError(-3);
  374.                 }
  375.                 else
  376.                 {
  377.                     OnRequestDone(uResponseCode, mHeaders, m_buffer.substr(uPos + 2));
  378.                 }
  379.             }
  380.             else
  381.                 OnRequestError(-2);
  382.         }
  383.         else
  384.             OnRequestError(-1);
  385.  
  386.         Close();
  387.     }
  388.  
  389. #if (VERSION_MAJOR == 0 && VERSION_MINOR <= 80)
  390.     void ReadData(const char *data, int len)
  391. #else
  392.     void ReadData(const char *data, size_t len)
  393. #endif
  394.     {
  395.         if(m_buffer.size() + len > m_maxBuf)
  396.         {
  397.             // make sure our buffers don't EVER take up too much memory.
  398.             // we just abort stuff in this case.
  399.             m_buffer.clear();
  400.             Close();
  401.         }
  402.         else
  403.         {
  404.             m_buffer.append(data, len);
  405.         }
  406.     }
  407.  
  408.     virtual void OnRequestDone(unsigned int uResponseCode, map<const CString, CString>& mHeaders, const CString& sResponse) = 0;
  409.     virtual void OnRequestError(int iErrorCode) = 0;
  410. };
  411.  
  412.  
  413. /************************************************************************/
  414. /* UTF-8 STUFF                                                          */
  415. /************************************************************************/
  416.  
  417. static CString Iso88591toUtf8(const CString& sString)
  418. {
  419.     CString sResult;
  420.     sResult.reserve(sString.size() * 2);
  421.  
  422.     for(CString::size_type p = 0; p < sString.size(); p++)
  423.     {
  424.         unsigned char c = sString[p];
  425.  
  426.         if(c < 0x80)
  427.         {
  428.             sResult += c;
  429.         }
  430.         else if(c < 0xC0)
  431.         {
  432.             sResult += '\xC2';
  433.             sResult += c;
  434.         }
  435.         else
  436.         {
  437.             sResult += '\xC3';
  438.             sResult += (c - 64);
  439.         }
  440.     }
  441.  
  442.     return sResult;
  443. }
  444.  
  445. namespace twiconv
  446. {
  447.     typedef wchar_t ucs4_t;
  448.     typedef void* conv_t;
  449.  
  450. #define RET_ILSEQ -1
  451. #define RET_TOOFEW(X) -2
  452. #define RET_TOOSMALL -3
  453. #define RET_ILUNI -4
  454.  
  455.     /*
  456.     * Copyright (C) 1999-2001, 2004 Free Software Foundation, Inc.
  457.     * This file is part of the GNU LIBICONV Library.
  458.     *
  459.     * The GNU LIBICONV Library is free software; you can redistribute it
  460.     * and/or modify it under the terms of the GNU Library General Public
  461.     * License as published by the Free Software Foundation; either version 2
  462.     * of the License, or (at your option) any later version.
  463.     **/
  464.  
  465.     static int
  466.         utf8_mbtowc (conv_t conv, ucs4_t *pwc, const unsigned char *s, int n)
  467.     {
  468.         unsigned char c = s[0];
  469.  
  470.         if (c < 0x80) {
  471.             *pwc = c;
  472.             return 1;
  473.         } else if (c < 0xc2) {
  474.             return RET_ILSEQ;
  475.         } else if (c < 0xe0) {
  476.             if (n < 2)
  477.                 return RET_TOOFEW(0);
  478.             if (!((s[1] ^ 0x80) < 0x40))
  479.                 return RET_ILSEQ;
  480.             *pwc = ((ucs4_t) (c & 0x1f) << 6)
  481.                 | (ucs4_t) (s[1] ^ 0x80);
  482.             return 2;
  483.         } else if (c < 0xf0) {
  484.             if (n < 3)
  485.                 return RET_TOOFEW(0);
  486.             if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40
  487.                 && (c >= 0xe1 || s[1] >= 0xa0)))
  488.                 return RET_ILSEQ;
  489.             *pwc = ((ucs4_t) (c & 0x0f) << 12)
  490.                 | ((ucs4_t) (s[1] ^ 0x80) << 6)
  491.                 | (ucs4_t) (s[2] ^ 0x80);
  492.             return 3;
  493.         } else if (c < 0xf8 && sizeof(ucs4_t)*8 >= 32) {
  494.             if (n < 4)
  495.                 return RET_TOOFEW(0);
  496.             if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40
  497.                 && (s[3] ^ 0x80) < 0x40
  498.                 && (c >= 0xf1 || s[1] >= 0x90)))
  499.                 return RET_ILSEQ;
  500.             *pwc = ((ucs4_t) (c & 0x07) << 18)
  501.                 | ((ucs4_t) (s[1] ^ 0x80) << 12)
  502.                 | ((ucs4_t) (s[2] ^ 0x80) << 6)
  503.                 | (ucs4_t) (s[3] ^ 0x80);
  504.             return 4;
  505.         } else if (c < 0xfc && sizeof(ucs4_t)*8 >= 32) {
  506.             if (n < 5)
  507.                 return RET_TOOFEW(0);
  508.             if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40
  509.                 && (s[3] ^ 0x80) < 0x40 && (s[4] ^ 0x80) < 0x40
  510.                 && (c >= 0xf9 || s[1] >= 0x88)))
  511.                 return RET_ILSEQ;
  512.             *pwc = ((ucs4_t) (c & 0x03) << 24)
  513.                 | ((ucs4_t) (s[1] ^ 0x80) << 18)
  514.                 | ((ucs4_t) (s[2] ^ 0x80) << 12)
  515.                 | ((ucs4_t) (s[3] ^ 0x80) << 6)
  516.                 | (ucs4_t) (s[4] ^ 0x80);
  517.             return 5;
  518.         } else if (c < 0xfe && sizeof(ucs4_t)*8 >= 32) {
  519.             if (n < 6)
  520.                 return RET_TOOFEW(0);
  521.             if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40
  522.                 && (s[3] ^ 0x80) < 0x40 && (s[4] ^ 0x80) < 0x40
  523.                 && (s[5] ^ 0x80) < 0x40
  524.                 && (c >= 0xfd || s[1] >= 0x84)))
  525.                 return RET_ILSEQ;
  526.             *pwc = ((ucs4_t) (c & 0x01) << 30)
  527.                 | ((ucs4_t) (s[1] ^ 0x80) << 24)
  528.                 | ((ucs4_t) (s[2] ^ 0x80) << 18)
  529.                 | ((ucs4_t) (s[3] ^ 0x80) << 12)
  530.                 | ((ucs4_t) (s[4] ^ 0x80) << 6)
  531.                 | (ucs4_t) (s[5] ^ 0x80);
  532.             return 6;
  533.         } else
  534.             return RET_ILSEQ;
  535.     }
  536.  
  537.     static int
  538.         utf8_wctomb (conv_t conv, unsigned char *r, ucs4_t wc, int n) /* n == 0 is acceptable */
  539.     {
  540.         int count;
  541.         if (wc < 0x80)
  542.             count = 1;
  543.         else if (wc < 0x800)
  544.             count = 2;
  545.         else if (wc < 0x10000)
  546.             count = 3;
  547.         else if (wc < 0x200000)
  548.             count = 4;
  549.         else if (wc < 0x4000000)
  550.             count = 5;
  551.         else if (wc <= 0x7fffffff)
  552.             count = 6;
  553.         else
  554.             return RET_ILUNI;
  555.         if (n < count)
  556.             return RET_TOOSMALL;
  557.         switch (count) { /* note: code falls through cases! */
  558.     case 6: r[5] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x4000000;
  559.     case 5: r[4] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x200000;
  560.     case 4: r[3] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x10000;
  561.     case 3: r[2] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x800;
  562.     case 2: r[1] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0xc0;
  563.     case 1: r[0] = (unsigned char)wc;
  564.         }
  565.         return count;
  566.     }
  567. };
  568.  
  569. // returned ar_length will only be valid if true was returned.
  570. static bool IsStrValidUTF8(const CString& sString, size_t &ar_length)
  571. {
  572.     const unsigned char *pStart = (unsigned char*)sString.c_str();
  573.     CString::size_type p = 0, uLen = sString.size();
  574.     wchar_t dummy;
  575.  
  576.     ar_length = 0;
  577.  
  578.     while(p < uLen)
  579.     {
  580.         int consumed = twiconv::utf8_mbtowc(NULL, &dummy, (unsigned char*)(pStart + p), uLen - p);
  581.  
  582.         if(consumed <= 0)
  583.             return false;
  584.  
  585.         ar_length++;
  586.         p += consumed;
  587.     }
  588.  
  589.     return true;
  590. }
  591.  
  592. static wstring Utf8ToWide(const string& sString)
  593. {
  594.     if(!sString.empty())
  595.     {
  596.         // a wide string can't have more characters than
  597.         // the same UTF-8 string has bytes, so this should be safe.
  598.         wchar_t *szBuf = new wchar_t[sString.size() + 1 + 6];
  599.         // we add 6 to avoid buffer overruns at any cost (see below).
  600.         *szBuf = 0;
  601.  
  602.         const unsigned char *p = (unsigned char*)sString.c_str();
  603.         wchar_t *b = szBuf, *bMax = szBuf + sString.size();
  604.  
  605.         while(*p && b <= bMax)
  606.         {
  607.             int consumed = twiconv::utf8_mbtowc(NULL, b, p, 6);
  608.  
  609.             if(consumed <= 0)
  610.                 return L"";
  611.  
  612.             p += consumed;
  613.             b++;
  614.         }
  615.  
  616.         if(b <= bMax)
  617.             *b = 0;
  618.         else
  619.             *bMax = 0;
  620.  
  621.         wstring sResult(szBuf);
  622.         delete[] szBuf;
  623.  
  624.         return sResult;
  625.     }
  626.  
  627.     return L"";
  628. }
  629.  
  630. static CString WideToUtf8(const wstring& sWString)
  631. {
  632.     if(sWString.size() > 0)
  633.     {
  634.         // find out how large our buffer needs to be.
  635.         size_t uBufSize = 0;
  636.         unsigned char szTmp[6];
  637.  
  638.         for(wstring::size_type p = 0; p < sWString.size(); p++)
  639.         {
  640.             int iBytes = twiconv::utf8_wctomb(NULL, &szTmp[0], sWString[p], 6);
  641.  
  642.             if(iBytes <= 0)
  643.                 return "";
  644.  
  645.             uBufSize += (unsigned int)iBytes;
  646.         }
  647.  
  648.         if(uBufSize > 0)
  649.         {
  650.             char *szBuf = new char[uBufSize + 1];
  651.             unsigned char *b = (unsigned char*)szBuf;
  652.  
  653.             *szBuf = 0;
  654.  
  655.             for(wstring::size_type p = 0; p < sWString.size(); p++)
  656.             {
  657.                 int iBytes = twiconv::utf8_wctomb(NULL, b, sWString[p], 6);
  658.  
  659.                 if(iBytes <= 0)
  660.                     break;
  661.  
  662.                 b += iBytes;
  663.             }
  664.  
  665.             *b = 0;
  666.  
  667.             CString sResult(szBuf);
  668.             delete[] szBuf;
  669.             return sResult;
  670.         }
  671.     }
  672.  
  673.     return "";
  674. }
  675.  
  676. /************************************************************************/
  677. /* UTF-8 STUFF FOR XML                                                  */
  678. /************************************************************************/
  679.  
  680. static CString Utf8Xml_Encode(const CString& sString)
  681. {
  682.     return sString.Replace_n("&", "&amp;")
  683.         .Replace_n("<", "&lt;")
  684.         .Replace_n(">", "&gt;")
  685.         .Replace_n("\"", "&quot;");
  686. }
  687.  
  688. #define FAST_ISDIGIT(C) (C >= '0' && C <= '9')
  689. #define FAST_ISDIGITX(C) (FAST_ISDIGIT(C) || (C >= 'a' && C <= 'f') || (C >= 'A' && C <= 'F'))
  690.  
  691. static CString Utf8Xml_Decode(const CString& sString)
  692. {
  693.     wstring swStr;
  694.     swStr.reserve(sString.size());
  695.  
  696.     CString::size_type p = sString.find('&'), pPrev = 0;
  697.  
  698.     do
  699.     {
  700.         if(p == CString::npos)
  701.         {
  702.             // handle rest of the str...
  703.             swStr += Utf8ToWide(sString.substr(pPrev));
  704.             break;
  705.         }
  706.         else
  707.         {
  708.             swStr += Utf8ToWide(sString.substr(pPrev, p - pPrev));
  709.         }
  710.  
  711.         bool bIgnore = true;
  712.  
  713.         CString::size_type pEnd = sString.find(';', p);
  714.         if(pEnd != CString::npos)
  715.         {
  716.             const string sEntity = sString.substr(p + 1, pEnd - p - 1);
  717.  
  718.             if(sEntity.size() >= 2 && sEntity[0] == '#')
  719.             {
  720.                 bool bHex = (sEntity[1] == 'x');
  721.                 bool bOk = true;
  722.  
  723.                 for(CString::size_type ep = 2; bOk && ep < sEntity.size(); ep++)
  724.                 {
  725.                     bOk = (bHex ? FAST_ISDIGITX(sEntity[ep]) : FAST_ISDIGIT(sEntity[ep]));
  726.                 }
  727.  
  728.                 if(bOk && (!bHex || sEntity.size() >= 3))
  729.                 {
  730.                     const unsigned long long lle = strtoull(sEntity.c_str() + (bHex ? 2 : 1), NULL, (bHex ? 16 : 10));
  731.  
  732.                     if(lle > 0 && lle <= numeric_limits<unsigned short>::max())
  733.                     {
  734.                         swStr += (wchar_t)lle;
  735.                         bIgnore = false;
  736.                     }
  737.                 }
  738.             }
  739.             else if(sEntity == "amp") { swStr += L'&'; bIgnore = false; }
  740.             else if(sEntity == "gt") { swStr += L'>'; bIgnore = false; }
  741.             else if(sEntity == "lt") { swStr += L'<'; bIgnore = false; }
  742.             else if(sEntity == "quot") { swStr += L'"'; bIgnore = false; }
  743.         }
  744.         else
  745.             pEnd = p + 1;
  746.  
  747.         if(!bIgnore)
  748.         {
  749.             pPrev = pEnd + 1;
  750.         }
  751.  
  752.         p = sString.find('&', pEnd);
  753.  
  754.     } while(true);
  755.  
  756.     return WideToUtf8(swStr);
  757. }
  758.  
  759. static CString Utf8Xml_NamedEntityDecode(const CString& sString)
  760. {
  761.     return sString.Replace_n("&quot;", "\"")
  762.         .Replace_n("&gt;", ">")
  763.         .Replace_n("&lt;", "<")
  764.         .Replace_n("&amp;", "&");
  765. }
  766.  
  767.  
  768. /************************************************************************/
  769. /* XML STUFF                                                            */
  770. /************************************************************************/
  771.  
  772. class CXMLException
  773. {
  774. protected:
  775.     CString m_message;
  776.     size_t m_pos;
  777. public:
  778.     CXMLException(const CString& sMessage, size_t iPos)
  779.     {
  780.         m_message = sMessage;
  781.         m_pos = iPos;
  782.     }
  783.     const CString GetMessage() const { return m_message; }
  784. };
  785.  
  786. class CXMLParser;
  787. class CXMLTag;
  788. typedef CSmartPtr<CXMLTag> PXMLTag;
  789.  
  790. class CXMLTag
  791. {
  792.     friend class CXMLParser;
  793. protected:
  794.     CString m_name;
  795.     map<const CString, CString> m_attributes;
  796.     vector<PXMLTag> m_children;
  797.     CString m_text;
  798.  
  799.     CXMLTag()
  800.     {
  801.     }
  802. public:
  803.     const CString& GetName() const
  804.     {
  805.         return m_name;
  806.     }
  807.  
  808.     const CString GetAttribute(const CString& sName) const
  809.     {
  810.         map<const CString, CString>::const_iterator it = m_attributes.find(sName);
  811.         return (it != m_attributes.end() ? it->second : "");
  812.     }
  813.  
  814.     const PXMLTag GetChildByName(const CString& sTagName) const
  815.     {
  816.         for(vector<PXMLTag>::const_iterator it = m_children.begin(); it != m_children.end(); it++)
  817.         {
  818.             if((*it)->m_name.Equals(sTagName))
  819.             {
  820.                 return *it;
  821.             }
  822.         }
  823.         return PXMLTag();
  824.     }
  825.  
  826.     const CString GetChildText(const CString& sTagName) const
  827.     {
  828.         const PXMLTag xTag = GetChildByName(sTagName);
  829.         return (xTag ? xTag->m_text : "");
  830.     }
  831.  
  832.     const vector<PXMLTag>::const_iterator BeginChildren() const
  833.     {
  834.         return m_children.begin();
  835.     }
  836.  
  837.     const vector<PXMLTag>::const_iterator EndChildren() const
  838.     {
  839.         return m_children.end();
  840.     }
  841. };
  842.  
  843. class CXMLParser
  844. {
  845. protected:
  846.     // will miss some things such as the bar in <foo lol="wat" bar>, but whatever.
  847.     static void ParseAttributes(const CString& sTagContents, map<const CString, CString>& mAttributes)
  848.     {
  849.         CString::size_type pos = sTagContents.find(" ");
  850.         bool bInName = true, bWaitingForVal = false, bInVal = false;
  851.         char cQuote = 0;
  852.         CString::size_type valStartPos = 0;
  853.         CString sName;
  854.  
  855.         while(pos != CString::npos && pos < sTagContents.size())
  856.         {
  857.             if(bInName && isspace(sTagContents[pos]))
  858.             {
  859.                 pos++;
  860.             }
  861.             else if(bInName && sTagContents[pos] == '=')
  862.             {
  863.                 bInName = false;
  864.                 bWaitingForVal = true;
  865.                 pos = sTagContents.find_first_of("'\"", pos + 1);
  866.             }
  867.             else if(bInName)
  868.             {
  869.                 sName += sTagContents[pos];
  870.                 pos++;
  871.             }
  872.             else if(bWaitingForVal && (sTagContents[pos] == '"' || sTagContents[pos] == '\''))
  873.             {
  874.                 cQuote = sTagContents[pos];
  875.                 bInVal = true;
  876.                 bWaitingForVal = false;
  877.                 valStartPos = pos + 1;
  878.                 pos = sTagContents.find(cQuote, pos + 1);
  879.             }
  880.             else if(bInVal && sTagContents[pos] == cQuote)
  881.             {
  882.                 bInVal = false;
  883.                 bInName = true;
  884.                 mAttributes[sName] = sTagContents.substr(valStartPos, pos - valStartPos);
  885.                 mAttributes[sName] = Utf8Xml_Decode(mAttributes[sName]);
  886.                 sName = "";
  887.  
  888.                 pos++;
  889.             }
  890.             else
  891.                 throw CXMLException("internal problem while parsing attributes", 0);
  892.         }
  893.  
  894.         if(bInVal || bWaitingForVal)
  895.         {
  896.             throw CXMLException("Couldn't parse some tag attributes: <" + sTagContents + ">", 0);
  897.         }
  898.     }
  899. public:
  900.     static PXMLTag ParseString(const CString& sXmlString)
  901.     {
  902.         stack<PXMLTag> m_openTags;
  903.         stack<PXMLTag> m_openTagParent;
  904.  
  905.         PXMLTag xParent, xRoot;
  906.         bool bEnding = false;
  907.  
  908.         CString::size_type pos = sXmlString.find("<");
  909.         CString::size_type iTextStartPos = CString::npos;
  910.         unsigned int iIteration = 0, iTextStartPosIteration = 0;
  911.  
  912.         while(pos != CString::npos)
  913.         {
  914.             if(bEnding)
  915.             {
  916.                 throw CXMLException("Multiple root tags?", pos);
  917.             }
  918.  
  919.             iIteration++;
  920.             CString::size_type tagendpos = sXmlString.find(">", pos + 1);
  921.  
  922.             if(tagendpos == CString::npos)
  923.             {
  924.                 throw CXMLException("No terminating > for open <", pos);
  925.             }
  926.  
  927.             if(tagendpos == pos + 1)
  928.             {
  929.                 throw CXMLException("Empty tag", pos);
  930.             }
  931.  
  932.             const CString sTagContents = sXmlString.substr(pos + 1, tagendpos - pos - 1);
  933.             CString sTagName = sTagContents.Token(0).Replace_n("\r", "").Replace_n("\n", "");
  934.  
  935.             if(sTagName.substr(0, 3) == "!--")
  936.             {
  937.                 // skip comments.
  938.                 tagendpos = sXmlString.find("-->", pos) + 2;
  939.  
  940.                 if(tagendpos == CString::npos)
  941.                 {
  942.                     throw CXMLException("Unterminated comment", pos);
  943.                 }
  944.             }
  945.             else if(sTagName[0] == '?')
  946.             {
  947.                 // skip <?xml stuff without any further checks.
  948.             }
  949.             else if(sTagName[0] != '/')
  950.             {
  951.                 // found start tag
  952.                 PXMLTag xNew(new CXMLTag());
  953.                 xNew->m_name = sTagName;
  954.  
  955.                 ParseAttributes(sTagContents, xNew->m_attributes);
  956.  
  957.                 // look out for <img /> style tags:
  958.                 if(sTagContents[sTagContents.size() - 1] != '/')
  959.                 {
  960.                     m_openTags.push(xNew);
  961.                     if(xParent) m_openTagParent.push(xParent);
  962.                     xParent = xNew;
  963.  
  964.                     // save the position in case this tag has no child tags
  965.                     // and we want to extract text from it.
  966.                     iTextStartPos = tagendpos + 1;
  967.                     iTextStartPosIteration = iIteration;
  968.                 }
  969.                 else if(xParent)
  970.                 {
  971.                     xParent->m_children.push_back(xNew);
  972.                 }
  973.                 else
  974.                 {
  975.                     xRoot = xNew;
  976.                 }
  977.             }
  978.             else
  979.             {
  980.                 // found end tag
  981.                 sTagName.erase(0, 1);
  982.  
  983.                 if(m_openTags.size() == 0 || !m_openTags.top()->m_name.Equals(sTagName))
  984.                 {
  985.                     throw CXMLException("Ending tag for '" + CString(sTagName) + "', which is not open", pos);
  986.                 }
  987.  
  988.                 // take the now-closed tag off the stack:
  989.                 PXMLTag xClosedTag = m_openTags.top();
  990.  
  991.                 m_openTags.pop();
  992.  
  993.                 // if no other tags have been found inbetween, extract text:
  994.                 if(iIteration == iTextStartPosIteration + 1)
  995.                 {
  996.                     xClosedTag->m_text = sXmlString.substr(iTextStartPos, pos - iTextStartPos);
  997.                     xClosedTag->m_text = Utf8Xml_Decode(xClosedTag->m_text);
  998.                 }
  999.  
  1000.                 // no parent = root tag. if this happens, we've walked the tree and are done.
  1001.                 if(m_openTagParent.empty())
  1002.                 {
  1003.                     xRoot = xClosedTag;
  1004.                     bEnding = true;
  1005.                 }
  1006.                 else
  1007.                 {
  1008.                     // re-set old parent:
  1009.                     xParent = m_openTagParent.top();
  1010.                     m_openTagParent.pop();
  1011.  
  1012.                     // otherwise, save the new child and to the next tag...
  1013.                     xParent->m_children.push_back(xClosedTag);
  1014.                 }
  1015.             }
  1016.  
  1017.             pos = sXmlString.find("<", tagendpos + 1);
  1018.         }
  1019.  
  1020.         if(m_openTags.size() != 0)
  1021.         {
  1022.             throw CXMLException("Found unclosed tags", 0);
  1023.         }
  1024.  
  1025.         return xRoot;
  1026.     }
  1027. };
  1028.  
  1029.  
  1030. /************************************************************************/
  1031. /* TWITTER HTTP REQUEST                                                 */
  1032. /************************************************************************/
  1033.  
  1034. class CTwitterHTTPSock : public CSimpleHTTPSock
  1035. {
  1036. protected:
  1037.     bool m_timedOut;
  1038.     CString m_method;
  1039.     bool m_needsAuth;
  1040.     CString m_host;
  1041.     CString m_path;
  1042.     bool m_bHideErrors;
  1043.  
  1044.     CTwitterHTTPSock(CTwitterModule *pModInstance, const CString& sMethod, bool bSilentErrors = true) :
  1045.         CSimpleHTTPSock(pModInstance), m_method(sMethod), m_bHideErrors(bSilentErrors)
  1046.     {
  1047.         m_timedOut = false;
  1048.         m_needsAuth = true;
  1049.         m_host = "api.twitter.com";
  1050.         m_path = "/1.1";
  1051.     }
  1052.  
  1053.     void Timeout()
  1054.     {
  1055.         if(!m_bHideErrors) m_pMod->PutModule("ERROR: Sorry, I failed to contact the Twitter servers. They may be down, again!");
  1056.         m_timedOut = true;
  1057.  
  1058.         CSimpleHTTPSock::Timeout();
  1059.     }
  1060.  
  1061.     CString SignString(const CString& sString)
  1062.     {
  1063.         return HMACSHA1(sString, TWITTER_CONSUMER_SECRET "&" + reinterpret_cast<CTwitterModule*>(m_pMod)->GetTokenSecret());
  1064.     }
  1065.  
  1066.     CString GenerateSignature(const CString& sHTTPMethod, const CString& sNormURL, const MCString& mParams)
  1067.     {
  1068.         CString sSigBaseStr;
  1069.  
  1070.         for(MCString::const_iterator it = mParams.begin(); it != mParams.end(); it++)
  1071.         {
  1072.             sSigBaseStr += URLEscape(it->first) + "=" + URLEscape(it->second) + "&";
  1073.         }
  1074.  
  1075.         if(!sSigBaseStr.empty())
  1076.         {
  1077.             sSigBaseStr.erase(sSigBaseStr.size() - 1);
  1078.             sSigBaseStr = URLEscape(sSigBaseStr);
  1079.         }
  1080.  
  1081.         sSigBaseStr = sHTTPMethod + "&" + URLEscape(sNormURL) + "&" + sSigBaseStr;
  1082.  
  1083.         DEBUG("[Twitter] OAuthSigBaseStr: -" << sSigBaseStr << "-");
  1084.  
  1085.         return SignString(sSigBaseStr);
  1086.     }
  1087.  
  1088.     CString GenerateNonce()
  1089.     {
  1090.         CString sTmp = m_method + "!" + CString(time(NULL)) + "@" + CString(getpid()) + "/" + CString(rand());
  1091.         return sTmp.MD5();
  1092.     }
  1093.  
  1094.     void PrepareParameters(const CString& sHTTPMethod, const CString& sNormURL, MCString& mParams, bool bAuthHeader)
  1095.     {
  1096.         MCString::iterator sigCheck = mParams.find("oauth_signature");
  1097.         if(sigCheck != mParams.end()) mParams.erase(sigCheck);
  1098.  
  1099.         if(m_needsAuth)
  1100.         {
  1101.             const CString sToken = reinterpret_cast<CTwitterModule*>(m_pMod)->GetToken();
  1102.  
  1103.             if(!sToken.empty()) mParams["oauth_token"] = sToken;
  1104.             mParams["oauth_consumer_key"] = TWITTER_CONSUMER_KEY;
  1105.             mParams["oauth_nonce"] = GenerateNonce();
  1106.             mParams["oauth_timestamp"] = CString(time(NULL));
  1107.             mParams["oauth_signature_method"] = "HMAC-SHA1";
  1108.             mParams["oauth_version"] = "1.0";
  1109.             mParams["oauth_signature"] = GenerateSignature(sHTTPMethod, sNormURL, mParams);
  1110.  
  1111.             if(bAuthHeader)
  1112.             {
  1113.                 CString sHeader = "OAuth ";
  1114.  
  1115.                 for(MCString::iterator it = mParams.begin(); it != mParams.end(); it++)
  1116.                 {
  1117.                     if(it->first.substr(0, 6) == "oauth_")
  1118.                     {
  1119.                         sHeader += URLEscape(it->first) + "=\"" + URLEscape(it->second) + "\",";
  1120.                     }
  1121.                 }
  1122.                 sHeader.erase(sHeader.size() - 1);
  1123.  
  1124.                 m_extraReqHeaders["Authorization"] = sHeader;
  1125.             }
  1126.         }
  1127.     }
  1128.  
  1129.     void DoRequest(const CString& sHTTPMethod, const MCString& mParams)
  1130.     {
  1131.         CString sUrl = "https://" + m_host + m_path + "/" + m_method;
  1132.  
  1133.         MCString mParamsCopy(mParams);
  1134.         PrepareParameters(sHTTPMethod, sUrl, mParamsCopy, (sHTTPMethod != "POST"));
  1135.  
  1136.         if(sHTTPMethod == "POST")
  1137.         {
  1138.             Post(mParamsCopy, m_host, m_path + "/" + m_method, 443, true);
  1139.         }
  1140.         else
  1141.         {
  1142.             CString sQuery = "?";
  1143.             for(MCString::const_iterator it = mParams.begin(); it != mParams.end(); it++)
  1144.             {
  1145.                 sQuery += URLEscape(it->first) + "=" + URLEscape(it->second) + "&";
  1146.             }
  1147.             sQuery.erase(sQuery.size() - 1);
  1148.  
  1149.             Get(m_host, m_path + "/" + m_method + sQuery, 443, true);
  1150.         }
  1151.     }
  1152.  
  1153.     void HandleCommonHTTPErrors(unsigned int uResponseCode, map<const CString, CString>& mHeaders, const CString& sResponse, bool bLogOutOn401)
  1154.     {
  1155.         CTwitterModule *pMod = reinterpret_cast<CTwitterModule*>(m_pMod);
  1156.  
  1157.         if(uResponseCode == 401 && bLogOutOn401)
  1158.         {
  1159.             pMod->EraseSession(true);
  1160.         }
  1161.         else if(!m_bHideErrors)
  1162.         {
  1163.             pMod->PutModule("ERROR: " + m_method + " returned HTTP code " + CString(uResponseCode));
  1164.  
  1165.             if(sResponse.Left(5) == "<?xml")
  1166.             {
  1167.                 CString sInfo = sResponse.Replace_n("\r", "").Replace_n("\n", "");
  1168.                 pMod->PutModule(sInfo.Left(400) + "...");
  1169.             }
  1170.         }
  1171.     }
  1172.  
  1173.     virtual void OnRequestError(int iErrorCode)
  1174.     {
  1175.         if(!m_bHideErrors) m_pMod->PutModule("ERROR: " + m_method + " failed (" + CString(iErrorCode) + ")");
  1176.     }
  1177. };
  1178.  
  1179.  
  1180. /************************************************************************/
  1181. /* (ACCESS) TOKEN REQUEST                                               */
  1182. /************************************************************************/
  1183.  
  1184. class CTRRequestToken : public CTwitterHTTPSock
  1185. {
  1186. protected:
  1187.     bool m_accessToken;
  1188. public:
  1189.     CTRRequestToken(CTwitterModule *pModInstance) :
  1190.         CTwitterHTTPSock(pModInstance, "oauth/request_token", false)
  1191.     {
  1192.         m_accessToken = false;
  1193.         m_path = "";
  1194.     }
  1195.  
  1196.     void Request(const CString& sAccessTokenPIN)
  1197.     {
  1198.         MCString mParams;
  1199.  
  1200.         if(!sAccessTokenPIN.empty())
  1201.         {
  1202.             m_method = "oauth/access_token";
  1203.             mParams["oauth_verifier"] = sAccessTokenPIN;
  1204.             m_accessToken = true;
  1205.         }
  1206.  
  1207.         DoRequest("POST", mParams);
  1208.     }
  1209.  
  1210.     void OnRequestDone(unsigned int uResponseCode, map<const CString, CString>& mHeaders, const CString& sResponse)
  1211.     {
  1212.         CTwitterModule *pMod = reinterpret_cast<CTwitterModule*>(m_pMod);
  1213.  
  1214.         if(uResponseCode == 200)
  1215.         {
  1216.             CString sTmp(sResponse);
  1217.             VCString vPairs;
  1218.  
  1219.             sTmp.Trim();
  1220.             sTmp.Split("&", vPairs, false);
  1221.  
  1222.             for(VCString::const_iterator it = vPairs.begin(); it != vPairs.end(); it++)
  1223.             {
  1224.                 CString sKey = it->Token(0, false, "="),
  1225.                     sValue = it->Token(1, true, "=");
  1226.  
  1227.                 if(sKey == "oauth_token")
  1228.                 {
  1229.                     pMod->m_token = sValue;
  1230.                 }
  1231.                 else if(sKey == "screen_name")
  1232.                 {
  1233.                     pMod->m_screenName = sValue;
  1234.                 }
  1235.                 else if(sKey == "user_id")
  1236.                 {
  1237.                     pMod->m_userId = sValue.ToULongLong();
  1238.                 }
  1239.                 else if(sKey == "oauth_token_secret")
  1240.                 {
  1241.                     pMod->m_tokenSecret = sValue;
  1242.                 }
  1243.             }
  1244.  
  1245.             if(!pMod->m_token.empty() && !pMod->m_tokenSecret.empty())
  1246.             {
  1247.                 if(m_accessToken)
  1248.                 {
  1249.                     pMod->PutModule("Welcome " + pMod->m_screenName + "! The Twitter module is now ready for action!");
  1250.  
  1251.                     pMod->m_waitingForPIN = false;
  1252.                     pMod->m_hasAccessToken = true;
  1253.  
  1254.                     pMod->SaveSettings();
  1255.                 }
  1256.                 else
  1257.                 {
  1258.                     pMod->m_waitingForPIN = true;
  1259.  
  1260.                     pMod->PutModule("Please open this page in your browser to authorize ZNC: https://twitter.com/oauth/authorize?oauth_callback=oob&oauth_token=" + pMod->m_token);
  1261.                     pMod->PutModule("If you allowed ZNC access, now enter the PIN code from the web page!");
  1262.                 }
  1263.             }
  1264.             else
  1265.             {
  1266.                 pMod->PutModule("ERROR: " + m_method + " returned weird stuff.");
  1267.             }
  1268.         }
  1269.         else
  1270.             HandleCommonHTTPErrors(uResponseCode, mHeaders, sResponse, false);
  1271.     }
  1272. };
  1273.  
  1274.  
  1275. /************************************************************************/
  1276. /* STATUS UPDATE REQUEST                                                */
  1277. /************************************************************************/
  1278.  
  1279. class CTRStatusUpdate : public CTwitterHTTPSock
  1280. {
  1281. protected:
  1282.     bool m_accessToken;
  1283. public:
  1284.     CTRStatusUpdate(CTwitterModule *pModInstance) :
  1285.         CTwitterHTTPSock(pModInstance, "statuses/update.xml", false)
  1286.     {
  1287.         m_accessToken = false;
  1288.     }
  1289.  
  1290.     void Request(const CString& sMessage)
  1291.     {
  1292.         MCString mParams;
  1293.  
  1294.         mParams["status"] = sMessage;
  1295.  
  1296.         DoRequest("POST", mParams);
  1297.     }
  1298.  
  1299.     void OnRequestDone(unsigned int uResponseCode, map<const CString, CString>& mHeaders, const CString& sResponse)
  1300.     {
  1301.         CTwitterModule *pMod = reinterpret_cast<CTwitterModule*>(m_pMod);
  1302.  
  1303.         if(uResponseCode == 200)
  1304.         {
  1305.             pMod->PutModule("Tweet sent!");
  1306.         }
  1307.         else
  1308.             HandleCommonHTTPErrors(uResponseCode, mHeaders, sResponse, true);
  1309.     }
  1310. };
  1311.  
  1312.  
  1313. /************************************************************************/
  1314. /* USER INFO REQUEST                                                    */
  1315. /************************************************************************/
  1316.  
  1317. class CTRUserInfo : public CTwitterHTTPSock
  1318. {
  1319. public:
  1320.     CTRUserInfo(CTwitterModule *pModInstance) :
  1321.         CTwitterHTTPSock(pModInstance, "users/show.xml", false)
  1322.     {
  1323.     }
  1324.  
  1325.     void Request()
  1326.     {
  1327.         MCString mParams;
  1328.  
  1329.         mParams["screen_name"] = reinterpret_cast<CTwitterModule*>(m_pMod)->GetScreenName();
  1330.  
  1331.         DoRequest("GET", mParams);
  1332.     }
  1333.  
  1334.     void OnRequestDone(unsigned int uResponseCode, map<const CString, CString>& mHeaders, const CString& sResponse)
  1335.     {
  1336.         CTwitterModule *pMod = reinterpret_cast<CTwitterModule*>(m_pMod);
  1337.  
  1338.         if(uResponseCode == 200)
  1339.         {
  1340.             PXMLTag xUser;
  1341.  
  1342.             try
  1343.             {
  1344.                 xUser = CXMLParser::ParseString(sResponse);
  1345.             }
  1346.             catch(CXMLException e)
  1347.             {
  1348.                 pMod->PutModule("ERROR: " + m_method + " (xml) " + e.GetMessage());
  1349.                 return;
  1350.             }
  1351.  
  1352.             CTable infoTable;
  1353.  
  1354.             infoTable.AddColumn("What");
  1355.             infoTable.AddColumn("Value");
  1356.  
  1357.             infoTable.AddRow();
  1358.             infoTable.SetCell("What", "URL");
  1359.             infoTable.SetCell("Value", "http://twitter.com/" + xUser->GetChildText("screen_name"));
  1360.  
  1361.             infoTable.AddRow();
  1362.             infoTable.SetCell("What", "Real Name");
  1363.             infoTable.SetCell("Value", xUser->GetChildText("name"));
  1364.  
  1365.             infoTable.AddRow();
  1366.             infoTable.SetCell("What", "Followers");
  1367.             infoTable.SetCell("Value", xUser->GetChildText("followers_count"));
  1368.  
  1369.             infoTable.AddRow();
  1370.             infoTable.SetCell("What", "Following");
  1371.             infoTable.SetCell("Value", xUser->GetChildText("friends_count"));
  1372.  
  1373.             infoTable.AddRow();
  1374.             infoTable.SetCell("What", "Tweets");
  1375.             infoTable.SetCell("Value", xUser->GetChildText("statuses_count"));
  1376.  
  1377.             pMod->PutModule(infoTable);
  1378.  
  1379.             if(PXMLTag xStatus = xUser->GetChildByName("status"))
  1380.             {
  1381.                 pMod->PutModule("Last Tweet: " + xStatus->GetChildText("text") + " [" + xStatus->GetChildText("created_at") + "]");
  1382.             }
  1383.         }
  1384.         else
  1385.             HandleCommonHTTPErrors(uResponseCode, mHeaders, sResponse, true);
  1386.     }
  1387. };
  1388.  
  1389.  
  1390. /************************************************************************/
  1391. /* USER INFO REQUEST                                                    */
  1392. /************************************************************************/
  1393.  
  1394. class CTRRateLimit : public CTwitterHTTPSock
  1395. {
  1396. public:
  1397.     CTRRateLimit(CTwitterModule *pModInstance) :
  1398.         CTwitterHTTPSock(pModInstance, "account/rate_limit_status.xml", false)
  1399.     {
  1400.     }
  1401.  
  1402.     void Request()
  1403.     {
  1404.         MCString mParams;
  1405.         DoRequest("GET", mParams);
  1406.     }
  1407.  
  1408.     void OnRequestDone(unsigned int uResponseCode, map<const CString, CString>& mHeaders, const CString& sResponse)
  1409.     {
  1410.         CTwitterModule *pMod = reinterpret_cast<CTwitterModule*>(m_pMod);
  1411.  
  1412.         if(uResponseCode == 200)
  1413.         {
  1414.             PXMLTag xHash;
  1415.  
  1416.             try
  1417.             {
  1418.                 xHash = CXMLParser::ParseString(sResponse);
  1419.             }
  1420.             catch(CXMLException e)
  1421.             {
  1422.                 pMod->PutModule("ERROR: " + m_method + " (xml) " + e.GetMessage());
  1423.                 return;
  1424.             }
  1425.  
  1426.             CTable infoTable;
  1427.  
  1428.             infoTable.AddColumn("What");
  1429.             infoTable.AddColumn("Value");
  1430.  
  1431.             infoTable.AddRow();
  1432.             infoTable.SetCell("What", "Remaining Hits");
  1433.             infoTable.SetCell("Value", xHash->GetChildText("remaining-hits") + " / " + xHash->GetChildText("hourly-limit"));
  1434.  
  1435.             infoTable.AddRow();
  1436.             infoTable.SetCell("What", "Reset Time");
  1437.             infoTable.SetCell("Value", CTwitterModule::FormatTweetTime(strtoull(xHash->GetChildText("reset-time-in-seconds").c_str(), NULL, 10), true));
  1438.  
  1439.             pMod->PutModule(infoTable);
  1440.         }
  1441.         else
  1442.             HandleCommonHTTPErrors(uResponseCode, mHeaders, sResponse, true);
  1443.     }
  1444. };
  1445.  
  1446.  
  1447. /************************************************************************/
  1448. /*      FEED REQUEST                                                    */
  1449. /************************************************************************/
  1450.  
  1451. class CTRFeed : public CTwitterHTTPSock
  1452. {
  1453. protected:
  1454.     bool m_initial;
  1455.     bool m_countSupported;
  1456.     int m_feedId;
  1457.     ETwitterFeedType m_feedType;
  1458. public:
  1459.     CTRFeed(CTwitterModule *pModInstance, ETwitterFeedType type, int iFeedId) :
  1460.         CTwitterHTTPSock(pModInstance, "", true)
  1461.     {
  1462.         m_countSupported = true;
  1463.         m_initial = false;
  1464.         m_feedId = iFeedId;
  1465.         m_feedType = type;
  1466.  
  1467.         if(type == TWFT_MENTIONS)
  1468.         {
  1469.             m_method = "statuses/mentions.atom";
  1470.         }
  1471.         else if(type == TWFT_SEARCH)
  1472.         {
  1473.             m_method = "search.atom";
  1474.             m_host = "search.twitter.com";
  1475.             m_path = "";
  1476.             m_needsAuth = false;
  1477.             m_countSupported = false;
  1478.         }
  1479.         else if(type == TWFT_TIMELINE)
  1480.         {
  1481.             m_method = "statuses/friends_timeline.atom";
  1482.         }
  1483.         else if(type == TWFT_USER)
  1484.         {
  1485.             m_method = "statuses/user_timeline.atom";
  1486.             m_needsAuth = (pModInstance->IsAuthed()); // needs auth for protected users only.
  1487.         }
  1488.     }
  1489.  
  1490.     void Request(bool bInitial, uint64_t iSinceId, const CString& sPayload)
  1491.     {
  1492.         MCString mParams;
  1493.  
  1494.         if(iSinceId > 0)
  1495.         {
  1496.             mParams["since_id"] = CString(iSinceId);
  1497.         }
  1498.  
  1499.         m_initial = bInitial;
  1500.  
  1501.         if(m_initial && m_countSupported)
  1502.         {
  1503.             mParams["count"] = "1";
  1504.         }
  1505.  
  1506.         if(m_feedType == TWFT_SEARCH)
  1507.         {
  1508.             mParams["q"] = sPayload;
  1509.             mParams["show_user"] = "true";
  1510.         }
  1511.         else if(m_feedType == TWFT_USER)
  1512.         {
  1513.             mParams["screen_name"] = sPayload;
  1514.         }
  1515.  
  1516.         DoRequest("GET", mParams);
  1517.     }
  1518.  
  1519.     void OnRequestDone(unsigned int uResponseCode, map<const CString, CString>& mHeaders, const CString& sResponse)
  1520.     {
  1521.         CTwitterModule *pMod = reinterpret_cast<CTwitterModule*>(m_pMod);
  1522.  
  1523.         if(uResponseCode == 200)
  1524.         {
  1525.             PXMLTag xRoot;
  1526.  
  1527.             try
  1528.             {
  1529.                 xRoot = CXMLParser::ParseString(sResponse);
  1530.             }
  1531.             catch(CXMLException e)
  1532.             {
  1533.                 pMod->PutModule("ERROR: " + m_method + " (xml) " + e.GetMessage());
  1534.                 return;
  1535.             }
  1536.  
  1537.             if(xRoot->GetName() != "feed")
  1538.             {
  1539.                 pMod->PutModule("ERROR: " + m_method + " -> no <feed> tag...");
  1540.                 return;
  1541.             }
  1542.  
  1543.             CTwitterFeed *fFeed = pMod->GetFeedById(m_feedId);
  1544.  
  1545.             if(!fFeed) return;
  1546.  
  1547.             for(vector<PXMLTag>::const_iterator it = xRoot->BeginChildren(); it != xRoot->EndChildren(); it++)
  1548.             {
  1549.                 const PXMLTag& xTag = *it;
  1550.  
  1551.                 if(xTag->GetName() == "entry")
  1552.                 {
  1553.                     CString sIdTmp = xTag->GetChildText("id");
  1554.                     CString sText = Utf8Xml_NamedEntityDecode(xTag->GetChildText("title")); // fix twitter bug.
  1555.                     sText = sText.Replace_n("\r", "").Replace_n("\n", " ");
  1556.  
  1557.                     CString::size_type uPos = sIdTmp.find("statuses/"), uPosAdd = 9;
  1558.                     if(uPos == CString::npos) { uPos = sIdTmp.rfind(':'); uPosAdd = 1; } // for search
  1559.  
  1560.                     if(uPos != CString::npos)
  1561.                     {
  1562.                         sIdTmp.erase(0, uPos + uPosAdd);
  1563.                         uint64_t uId = sIdTmp.ToULongLong();
  1564.                         time_t iTime = pMod->InterpretRFC3339Time(xTag->GetChildText("published"));
  1565.  
  1566.                         if(uId > 0 && iTime > 0)
  1567.                         {
  1568.                             if(uId > fFeed->m_lastId)
  1569.                             {
  1570.                                 fFeed->m_lastId = uId;
  1571.                             }
  1572.  
  1573.                             if(m_initial)
  1574.                             {
  1575.                                 break;
  1576.                             }
  1577.  
  1578.                             pMod->QueueMessage(fFeed, iTime, sText);
  1579.                         }
  1580.                     }
  1581.                 }
  1582.             }
  1583.  
  1584.             fFeed->m_lastUpdate = time(NULL);
  1585.         }
  1586.         else if(uResponseCode == 400)
  1587.         {
  1588.             PXMLTag xHash;
  1589.  
  1590.             try
  1591.             {
  1592.                 xHash = CXMLParser::ParseString(sResponse);
  1593.             }
  1594.             catch(CXMLException e)
  1595.             {
  1596.                 pMod->PutModule("ERROR: " + m_method + " (xml/error) " + e.GetMessage());
  1597.                 return;
  1598.             }
  1599.  
  1600.             pMod->PutModule("ERROR: Feed " + CString(m_feedId) + " returned an error: " + xHash->GetChildText("error"));
  1601.             pMod->PutModule("Disabling updates on this feed for 15 minutes.");
  1602.  
  1603.             CTwitterFeed *fFeed = pMod->GetFeedById(m_feedId);
  1604.  
  1605.             if(fFeed)
  1606.             {
  1607.                 fFeed->m_lastUpdate = time(NULL) + 60 * 15;
  1608.             }
  1609.         }
  1610.         else if(uResponseCode == 503 && m_feedType == TWFT_SEARCH)
  1611.         {
  1612.             CTwitterFeed *fFeed = pMod->GetFeedById(m_feedId);
  1613.  
  1614.             if(fFeed)
  1615.             {
  1616.                 // The Retry-After header's value is the number of seconds your
  1617.                 // application should wait before submitting another query.
  1618.                 fFeed->m_lastUpdate = time(NULL) + atoi(mHeaders["Retry-After"].c_str());
  1619.             }
  1620.         }
  1621.         else if(uResponseCode == 502) // "twitter is over capacity"
  1622.         {
  1623.             CTwitterFeed *fFeed = pMod->GetFeedById(m_feedId);
  1624.  
  1625.             // cut them servers some slack:
  1626.             if(fFeed)
  1627.             {
  1628.                 fFeed->m_lastUpdate = time(NULL) + 5 * 60;
  1629.             }
  1630.         }
  1631.         else if(m_needsAuth)
  1632.             HandleCommonHTTPErrors(uResponseCode, mHeaders, sResponse, true);
  1633.         else
  1634.             HandleCommonHTTPErrors(uResponseCode, mHeaders, sResponse, false);
  1635.     }
  1636. };
  1637.  
  1638.  
  1639. /************************************************************************/
  1640. /*       TIMER IMPLEMENTATION                                           */
  1641. /************************************************************************/
  1642.  
  1643. class CModTwitterTimer : public CTimer
  1644. {
  1645. public:
  1646.     CModTwitterTimer(CModule* pModule, unsigned int uInterval, unsigned int uCycles, const CString& sLabel, const CString& sDescription) : CTimer(pModule, uInterval, uCycles, sLabel, sDescription) {}
  1647.     ~CModTwitterTimer() {}
  1648. protected:
  1649.     void RunJob()
  1650.     {
  1651.         CTwitterModule *pMod = reinterpret_cast<CTwitterModule*>(m_pModule);
  1652.  
  1653.         pMod->TimerAction();
  1654.     }
  1655. };
  1656.  
  1657.  
  1658. /************************************************************************/
  1659. /*       CTwitterModule implementation                                  */
  1660. /************************************************************************/
  1661.  
  1662. static bool _feedQueueSortByID(const CTwitterFeed& a, const CTwitterFeed& b)
  1663. {
  1664.     return (a.m_id < b.m_id);
  1665. }
  1666.  
  1667. void CTwitterModule::OnModCommand(const CString& sCommand)
  1668. {
  1669.     const CString sCmd = sCommand.Token(0).AsUpper();
  1670.  
  1671.     if(sCmd == "HELP")
  1672.     {
  1673.         CTable CmdTable;
  1674.  
  1675.         CmdTable.AddColumn("Command");
  1676.         CmdTable.AddColumn("Description");
  1677.  
  1678.         CmdTable.AddRow();
  1679.         CmdTable.SetCell("Command", "LOGIN");
  1680.         CmdTable.SetCell("Description", "Use this command to initially set up the client.");
  1681.  
  1682.         CmdTable.AddRow();
  1683.         CmdTable.SetCell("Command", "TWEET <message>");
  1684.         CmdTable.SetCell("Description", "Posts a tweet to Twitter! You can also use /me <message> in this window.");
  1685.  
  1686.         CmdTable.AddRow();
  1687.         CmdTable.SetCell("Command", "STATUS");
  1688.         CmdTable.SetCell("Description", "Shows basic info about the logged in user.");
  1689.  
  1690.         CmdTable.AddRow();
  1691.         CmdTable.SetCell("Command", "WATCH (timeline|mentions|user <screenname>|search) [#channel|nick]");
  1692.         CmdTable.SetCell("Description", "Creates a feed to watch. user + search do not need a login. No target = this window. See also: CHANGE.");
  1693.  
  1694.         CmdTable.AddRow();
  1695.         CmdTable.SetCell("Command", "WATCH");
  1696.         CmdTable.SetCell("Description", "Lists all active feeds.");
  1697.  
  1698.         CmdTable.AddRow();
  1699.         CmdTable.SetCell("Command", "CHANGE <id> (prefix|target|query|colors) <new val>");
  1700.         CmdTable.SetCell("Description", "Changes a feed's settings, <id> is the number from the WATCH list. Colors = (on|off). Query only applies to searches.");
  1701.  
  1702.         CmdTable.AddRow();
  1703.         CmdTable.SetCell("Command", "STYLE (user|@user|hashtag|link|timestamp|text) ([bold] [underline] [fore[,back]]|off)");
  1704.         CmdTable.SetCell("Description", "Sets color and text style. Example: 'STYLE hashtag underline 4' would make all #hashtags red & underlined.");
  1705.  
  1706.         CmdTable.AddRow();
  1707.         CmdTable.SetCell("Command", "STYLE");
  1708.         CmdTable.SetCell("Description", "Shows active styles.");
  1709.  
  1710.         CmdTable.AddRow();
  1711.         CmdTable.SetCell("Command", "DELETE <id>");
  1712.         CmdTable.SetCell("Description", "Deletes the feed with the number <id> from the WATCH list.");
  1713.  
  1714.         CmdTable.AddRow();
  1715.         CmdTable.SetCell("Command", "HELP");
  1716.         CmdTable.SetCell("Description", "This help.");
  1717.  
  1718.         PutModule(CmdTable);
  1719.     }
  1720.     else if(sCmd == "WATCH" || sCmd == "SHOW")
  1721.     {
  1722.         const CString sSubCmd = sCommand.Token(1, false).AsLower();
  1723.  
  1724.         if((sSubCmd.empty() || sCmd == "SHOW") && m_feeds.size() > 0)
  1725.         {
  1726.             CTable tbl;
  1727.             static const char* feedTypeNames[_TWFT_MAX] = { "", "Timeline", "Mentions", "User", "Search" };
  1728.  
  1729.             tbl.AddColumn("ID");
  1730.             tbl.AddColumn("Type");
  1731.             tbl.AddColumn("Param");
  1732.             tbl.AddColumn("Target");
  1733.             tbl.AddColumn("Prefix");
  1734.             tbl.AddColumn("Updated");
  1735.  
  1736.             sort(m_feeds.begin(), m_feeds.end(), _feedQueueSortByID);
  1737.  
  1738.             for(vector<CTwitterFeed>::const_iterator it = m_feeds.begin(); it != m_feeds.end(); it++)
  1739.             {
  1740.                 tbl.AddRow();
  1741.                 tbl.SetCell("ID", CString(it->m_id));
  1742.                 tbl.SetCell("Type", feedTypeNames[it->m_type]);
  1743.                 tbl.SetCell("Param", it->m_payload.empty() ? "-" : it->m_payload);
  1744.                 tbl.SetCell("Target", it->m_target.empty() ? CString(CZNC::Get().GetStatusPrefix() + m_sModName) : it->m_target);
  1745.                 tbl.SetCell("Prefix", it->m_prefix);
  1746.                 tbl.SetCell("Updated", (it->m_lastUpdate == 0 ? CString("never") : FormatTweetTime(it->m_lastUpdate)));
  1747.             }
  1748.  
  1749.             PutModule(tbl);
  1750.         }
  1751.         else if(sSubCmd.empty() || sCmd == "SHOW")
  1752.         {
  1753.             PutModule("ERROR: No watches/feeds set up.");
  1754.         }
  1755.         else if(sSubCmd == "timeline" || sSubCmd == "mentions" || sSubCmd == "user" || sSubCmd == "search")
  1756.         {
  1757.             if(!m_hasAccessToken && (sSubCmd == "timeline" || sSubCmd == "mentions"))
  1758.             {
  1759.                 PutModule("ERROR: timeline and mentions need a logged in user. Use the LOGIN command.");
  1760.                 return;
  1761.             }
  1762.  
  1763.             const CString sToken2 = sCommand.Token(2),
  1764.                 sToken3 = sCommand.Token(3);
  1765.  
  1766.             if(sSubCmd == "user" && sToken2.empty())
  1767.             {
  1768.                 PutModule("ERROR: No user name specified.");
  1769.                 return;
  1770.             }
  1771.  
  1772.             CTwitterFeed fNew(
  1773.                 (sSubCmd == "timeline" ? TWFT_TIMELINE :
  1774.                 (sSubCmd == "mentions" ? TWFT_MENTIONS :
  1775.                 (sSubCmd == "user" ? TWFT_USER :
  1776.                 (sSubCmd == "search" ? TWFT_SEARCH : _TWFT_MAX)))));
  1777.             fNew.m_id = (m_nextFeedId++);
  1778.             fNew.m_lastId = 0;
  1779.             fNew.m_payload = (sSubCmd == "user" ? sToken2 : "");
  1780.             fNew.m_target = (sSubCmd == "user" ? sToken3 : sToken2);
  1781.  
  1782.             m_feeds.push_back(fNew);
  1783.  
  1784.             SaveSettings();
  1785.  
  1786.             PutModule("Added!");
  1787.         }
  1788.         else
  1789.         {
  1790.             PutModule("Command not understood. Have a look at HELP.");
  1791.         }
  1792.     }
  1793.     else if(sCmd == "CHANGE" || sCmd == "DELETE" || sCmd == "REMOVE")
  1794.     {
  1795.         int id = sCommand.Token(1).ToInt();
  1796.         vector<CTwitterFeed>::iterator it;
  1797.  
  1798.         for(it = m_feeds.begin(); it != m_feeds.end(); it++)
  1799.         {
  1800.             if(it->m_id == id)
  1801.                 break;
  1802.         }
  1803.  
  1804.         if(it == m_feeds.end())
  1805.         {
  1806.             PutModule("ERROR: No feed with ID " + CString(id) + " found.");
  1807.             return;
  1808.         }
  1809.  
  1810.         if(sCmd != "CHANGE")
  1811.         {
  1812.             m_feeds.erase(it);
  1813.             SaveSettings();
  1814.             PutModule("Feed deleted.");
  1815.             return;
  1816.         }
  1817.  
  1818.         const CString sWhat = sCommand.Token(2).AsLower();
  1819.         const CString sNew = sCommand.Token(3, true);
  1820.  
  1821.         if(sWhat == "prefix")
  1822.         {
  1823.             it->m_prefix = sNew;
  1824.             PutModule("Prefix changed.");
  1825.         }
  1826.         else if(sWhat == "target")
  1827.         {
  1828.             it->m_target = sNew;
  1829.             PutModule("Sending feed messages to " + (it->m_target.empty() ? CZNC::Get().GetStatusPrefix() + m_sModName : it->m_target) + " from now on.");
  1830.         }
  1831.         else if(sWhat == "colors" || sWhat == "color")
  1832.         {
  1833.             it->m_colors = (sNew == "1" || sNew == "true" || sNew == "on");
  1834.             PutModule("Colors are now '" + CString(it->m_colors ? "on" : "off") + "'.");
  1835.         }
  1836.         else if(sWhat == "query" && it->m_type == TWFT_SEARCH && !sNew.empty())
  1837.         {
  1838.             it->m_payload = sNew;
  1839.             PutModule("Query changed.");
  1840.         }
  1841.         else
  1842.         {
  1843.             PutModule("Command not understood. Have a look at HELP.");
  1844.         }
  1845.  
  1846.         SaveSettings();
  1847.     }
  1848.     else if(sCmd == "STYLE")
  1849.     {
  1850.         const CString sWhat = sCommand.Token(1).AsLower();
  1851.         bool bChangedSth = false;
  1852.  
  1853.         if(sWhat.empty())
  1854.         {
  1855.             CString sMsg = "Active styles: ";
  1856.  
  1857.             for(map<const CString, CFontStyle>::iterator it = m_styles.begin(); it != m_styles.end(); it++)
  1858.             {
  1859.                 sMsg += it->second.GetStartCode() + it->first + it->second.GetEndCode() + " / ";
  1860.             }
  1861.  
  1862.             sMsg.erase(sMsg.size() - 3);
  1863.  
  1864.             PutModule(sMsg);
  1865.             return;
  1866.         }
  1867.  
  1868.         map<const CString, CFontStyle>::iterator it = m_styles.find(sWhat);
  1869.  
  1870.         if(it != m_styles.end())
  1871.         {
  1872.             it->second = CFontStyle();
  1873.  
  1874.             for(size_t p = 2; p < 5; p++)
  1875.             {
  1876.                 const CString sStyle = sCommand.Token(p);
  1877.                 int l_fore = -1, l_back = -1;
  1878.  
  1879.                 if(sStyle == "bold")
  1880.                     { it->second.bBold = true; bChangedSth = true; }
  1881.                 else if(sStyle == "underline" || sStyle == "underlined")
  1882.                     { it->second.bUnderline = true;  bChangedSth = true; }
  1883.                 else if(sscanf(sStyle.c_str(), "%i,%i", &l_fore, &l_back) == 2 ||
  1884.                     sscanf(sStyle.c_str(), "%i", &l_fore) == 1)
  1885.                 {
  1886.                     it->second.iForeClr = (l_fore > 15 ? 15 : l_fore);
  1887.                     it->second.iBackClr = (l_back > 15 ? 15 : l_back);
  1888.                     bChangedSth = true;
  1889.                 }
  1890.                 else if(sStyle == "off")
  1891.                     bChangedSth = true;
  1892.             }
  1893.         }
  1894.  
  1895.         if(!bChangedSth)
  1896.         {
  1897.             PutModule("Command not understood. Have a look at HELP.");
  1898.         }
  1899.         else
  1900.         {
  1901.             PutModule("Changed style for " + it->second.GetStartCode() + sWhat);
  1902.             SaveSettings();
  1903.         }
  1904.     }
  1905.     else if(sCmd == "TWEET")
  1906.     {
  1907.         CString sMessage = sCommand.Token(1, true);
  1908.         SendTweet(sMessage);
  1909.     }
  1910.     else if(sCmd == "LOGIN")
  1911.     {
  1912.         if(m_hasAccessToken && sCommand.Token(1).AsLower() != "force")
  1913.         {
  1914.             PutModule("We are already authenticated with Twitter (user: " + m_screenName + "). Use LOGIN FORCE to ignore. Also check the HELP command!");
  1915.         }
  1916.         else
  1917.         {
  1918.             PutModule("Logging in to Twitter...");
  1919.  
  1920.             EraseSession(false);
  1921.  
  1922.             CTRRequestToken *pReq = new CTRRequestToken(this);
  1923.             pReq->Request("");
  1924.         }
  1925.     }
  1926.     else if(sCmd == "LOGOUT")
  1927.     {
  1928.         EraseSession(false);
  1929.  
  1930.         PutModule("Login data erased.");
  1931.     }
  1932.     else if(sCmd == "STATUS")
  1933.     {
  1934.         if(m_hasAccessToken)
  1935.         {
  1936.             PutModule("Getting status...");
  1937.  
  1938.             CTRUserInfo *pReq = new CTRUserInfo(this);
  1939.             pReq->Request();
  1940.         }
  1941.         else
  1942.         {
  1943.             PutModule("Not logged in. Use the LOGIN command.");
  1944.         }
  1945.     }
  1946.     else if(sCmd == "RATELIMIT")
  1947.     {
  1948.         if(m_hasAccessToken)
  1949.         {
  1950.             PutModule("Getting status...");
  1951.  
  1952.             CTRRateLimit *pReq = new CTRRateLimit(this);
  1953.             pReq->Request();
  1954.         }
  1955.         else
  1956.         {
  1957.             PutModule("Not logged in. Use the LOGIN command.");
  1958.         }
  1959.     }
  1960.     else if(m_waitingForPIN)
  1961.     {
  1962.         unsigned long long pin = sCommand.Token(0).ToULongLong();
  1963.  
  1964.         if(pin > 0)
  1965.         {
  1966.             PutModule("Checking PIN...");
  1967.  
  1968.             CTRRequestToken *pReq = new CTRRequestToken(this);
  1969.             pReq->Request(CString(pin));
  1970.         }
  1971.         else if(sCommand.Token(0).AsUpper() == "CANCEL")
  1972.         {
  1973.             m_waitingForPIN = false;
  1974.             m_token = m_tokenSecret = m_screenName = "";
  1975.  
  1976.             PutModule("Auth process cancelled. Use LOGIN to start over.");
  1977.         }
  1978.         else
  1979.         {
  1980.             PutModule("ERROR: This didn't look like a PIN code. Type CANCEL to start over.");
  1981.         }
  1982.     }
  1983.     else
  1984.     {
  1985.         PutModule("Unknown command! Try HELP or TWEET <message>.");
  1986.     }
  1987. }
  1988.  
  1989. void CTwitterModule::OnModCTCP(const CString& sMessage)
  1990. {
  1991.     if(sMessage.Token(0) == "ACTION")
  1992.     {
  1993.         SendTweet(sMessage.Token(1, true));
  1994.     }
  1995. }
  1996.  
  1997. void CTwitterModule::SendTweet(CString sMessage)
  1998. {
  1999.     size_t iUtf8Len = 0;
  2000.  
  2001.     if(!m_hasAccessToken)
  2002.     {
  2003.         PutModule("ERROR: Not logged in. Please use LOGIN to login!");
  2004.         return;
  2005.     }
  2006.  
  2007.     if(!IsStrValidUTF8(sMessage, iUtf8Len))
  2008.     {
  2009.         sMessage = Iso88591toUtf8(sMessage);
  2010.  
  2011.         if(!IsStrValidUTF8(sMessage, iUtf8Len))
  2012.         {
  2013.             PutModule("ERROR: Your client didn't send a valid UTF-8 or ISO-8859-1 encoded message!");
  2014.             return;
  2015.         }
  2016.     }
  2017.  
  2018.     if(iUtf8Len > 140)
  2019.     {
  2020.         PutModule("ERROR: Your message has " + CString(iUtf8Len) + " characters. Maximum allowed length is 140 characters.");
  2021.     }
  2022.     else
  2023.     {
  2024.         PutModule("Sending tweet...");
  2025.  
  2026.         CTRStatusUpdate *pReq = new CTRStatusUpdate(this);
  2027.         pReq->Request(sMessage);
  2028.     }
  2029. }
  2030.  
  2031. void CTwitterModule::EraseSession(bool bErrorMessage)
  2032. {
  2033.     m_hasAccessToken = m_waitingForPIN = false;
  2034.     m_token = m_tokenSecret = m_screenName = "";
  2035.  
  2036.     SaveSettings();
  2037.  
  2038.     if(bErrorMessage)
  2039.     {
  2040.         PutModule("ERROR: Session has expired. Please use LOGIN to login again.");
  2041.     }
  2042. }
  2043.  
  2044. bool CTwitterModule::OnLoad(const CString& sArgs, CString& sMessage)
  2045. {
  2046.     LoadSettings();
  2047.  
  2048.     AddTimer(new CModTwitterTimer(this, 1, 0, "TwitterModuleTimer", ""));
  2049.  
  2050.     return true;
  2051. }
  2052.  
  2053. void CTwitterModule::SaveSettings()
  2054. {
  2055.     ClearNV();
  2056.  
  2057.     if(m_hasAccessToken && !m_token.empty() && !m_tokenSecret.empty())
  2058.     {
  2059.         SetNV("token", m_token, false);
  2060.         SetNV("secret", m_tokenSecret, false);
  2061.         SetNV("screen_name", m_screenName, false);
  2062.     }
  2063.  
  2064.     CString sFeeds = "<feeds>";
  2065.     for(vector<CTwitterFeed>::const_iterator it = m_feeds.begin(); it != m_feeds.end(); it++)
  2066.     {
  2067.         sFeeds += "<feed type=\"" + CString(it->m_type) + "\">";
  2068.  
  2069.         if(!it->m_payload.empty()) sFeeds += "<payload>" + Utf8Xml_Encode(it->m_payload) + "</payload>";
  2070.         if(!it->m_prefix.empty()) sFeeds += "<prefix>" + Utf8Xml_Encode(it->m_prefix) + "</prefix>";
  2071.         if(!it->m_target.empty()) sFeeds += "<target>" + Utf8Xml_Encode(it->m_target) + "</target>";
  2072.  
  2073.         sFeeds += "</feed>";
  2074.     }
  2075.     sFeeds += "</feeds>";
  2076.  
  2077.     SetNV("feeds", sFeeds, false);
  2078.  
  2079.     CString sStyles = "<styles>";
  2080.     for(map<const CString, CFontStyle>::const_iterator it = m_styles.begin(); it != m_styles.end(); it++)
  2081.     {
  2082.         sStyles += "<style name=\"" + Utf8Xml_Encode(it->first) + "\">";
  2083.  
  2084.         sStyles += "<bold>" + CString(it->second.bBold ? 1 : 0) + "</bold>";
  2085.         sStyles += "<underline>" + CString(it->second.bUnderline ? 1 : 0) + "</underline>";
  2086.         sStyles += "<forecolor>" + CString(it->second.iForeClr) + "</forecolor>";
  2087.         sStyles += "<backcolor>" + CString(it->second.iBackClr) + "</backcolor>";
  2088.  
  2089.         sStyles += "</style>";
  2090.     }
  2091.     sStyles += "</styles>";
  2092.  
  2093.     SetNV("styles", sStyles, true);
  2094. }
  2095.  
  2096.  
  2097. void CTwitterModule::LoadSettings()
  2098. {
  2099.     LoadRegistry();
  2100.  
  2101.     m_token = GetNV("token");
  2102.     m_tokenSecret = GetNV("secret");
  2103.  
  2104.     if(!m_token.empty() && !m_tokenSecret.empty())
  2105.     {
  2106.         m_hasAccessToken = true;
  2107.  
  2108.         m_screenName = GetNV("screen_name");
  2109.     }
  2110.     else // ensure consistent state...
  2111.     {
  2112.         m_token = m_tokenSecret = "";
  2113.     }
  2114.  
  2115.     PXMLTag xFeeds;
  2116.  
  2117.     try
  2118.     {
  2119.         xFeeds = CXMLParser::ParseString(GetNV("feeds"));
  2120.     }
  2121.     catch (CXMLException ex)
  2122.     {
  2123.         PutModule("Warning: Couldn't read feeds from disk.");
  2124.     }
  2125.  
  2126.     if(xFeeds)
  2127.     {
  2128.         for(vector<PXMLTag>::const_iterator it = xFeeds->BeginChildren(); it != xFeeds->EndChildren(); it++)
  2129.         {
  2130.             const PXMLTag& xFeed = *it;
  2131.  
  2132.             int iType = xFeed->GetAttribute("type").ToInt();
  2133.  
  2134.             if(iType > 0 && iType < _TWFT_MAX)
  2135.             {
  2136.                 CTwitterFeed fNew((ETwitterFeedType)iType);
  2137.  
  2138.                 fNew.m_id = (m_nextFeedId++);
  2139.                 fNew.m_lastId = 0;
  2140.                 fNew.m_payload = xFeed->GetChildText("payload");
  2141.                 fNew.m_prefix = xFeed->GetChildText("prefix");
  2142.                 fNew.m_target = xFeed->GetChildText("target");
  2143.  
  2144.                 m_feeds.push_back(fNew);
  2145.             }
  2146.         }
  2147.     }
  2148.  
  2149.     PXMLTag xStyles;
  2150.  
  2151.     try
  2152.     {
  2153.         xStyles = CXMLParser::ParseString(GetNV("styles"));
  2154.     }
  2155.     catch (CXMLException ex)
  2156.     {
  2157.         PutModule("Warning: Couldn't read styles from disk.");
  2158.     }
  2159.  
  2160.     if(xStyles)
  2161.     {
  2162.         for(vector<PXMLTag>::const_iterator it = xStyles->BeginChildren(); it != xStyles->EndChildren(); it++)
  2163.         {
  2164.             const PXMLTag& xStyle = *it;
  2165.             map<const CString, CFontStyle>::iterator found = m_styles.find((*it)->GetAttribute("name"));
  2166.  
  2167.             if(found != m_styles.end())
  2168.             {
  2169.                 found->second.bBold = (xStyle->GetChildText("bold") == "1");
  2170.                 found->second.bUnderline = (xStyle->GetChildText("underline") == "1");
  2171.                 found->second.iBackClr = xStyle->GetChildText("backcolor").ToInt();
  2172.                 found->second.iForeClr = xStyle->GetChildText("forecolor").ToInt();
  2173.  
  2174.                 if(found->second.iBackClr > 15) found->second.iBackClr = 15;
  2175.                 if(found->second.iForeClr > 15) found->second.iForeClr = 15;
  2176.                 if(found->second.iBackClr < -1) found->second.iBackClr = -1;
  2177.                 if(found->second.iForeClr < -1) found->second.iForeClr = -1;
  2178.             }
  2179.         }
  2180.     }
  2181.  
  2182.     if(m_hasAccessToken && m_pUser && m_pUser->IsUserAttached())
  2183.     {
  2184.         CTRUserInfo *pReq = new CTRUserInfo(this);
  2185.         pReq->Request();
  2186.     }
  2187. }
  2188.  
  2189. CTwitterFeed *CTwitterModule::GetFeedById(int id)
  2190. {
  2191.     for(vector<CTwitterFeed>::iterator it = m_feeds.begin(); it != m_feeds.end(); it++)
  2192.     {
  2193.         if(it->m_id == id)
  2194.             return &(*it);
  2195.     }
  2196.     return NULL;
  2197. }
  2198.  
  2199. static bool _feedQueueSortByLastUpdate(const CTwitterFeed& a, const CTwitterFeed& b)
  2200. {
  2201.     return (a.m_lastUpdate < b.m_lastUpdate);
  2202. }
  2203.  
  2204. void CTwitterModule::TimerAction()
  2205. {
  2206.     int iMsgsSent = 0;
  2207.     bool bAllowBunch = (m_msgQueueLastSent < time(NULL) - 10);
  2208.  
  2209.     while(!m_msgQueue.empty())
  2210.     {
  2211.         vector<pair<time_t, CString> >::iterator it = m_msgQueue.begin();
  2212.  
  2213.         if(it->second.substr(0, 10) == "PRIVMSG  :")
  2214.         {
  2215.             // message to *twitter, no rate limiting required.
  2216.             PutModule(it->second.substr(10));
  2217.         }
  2218.         else if((iMsgsSent < 5 && bAllowBunch) || (m_msgQueueLastSent < time(NULL)))
  2219.         {
  2220.             iMsgsSent++;
  2221.             PutIRC(it->second);
  2222.             if(m_pUser) m_pUser->PutUser(":" + m_pUser->GetIRCNick().GetNickMask() + " " + it->second);
  2223.             m_msgQueueLastSent = time(NULL);
  2224.         }
  2225.         else
  2226.             break;
  2227.  
  2228.         m_msgQueue.erase(it);
  2229.     }
  2230.  
  2231.     // look at updates...
  2232.     sort(m_feeds.begin(), m_feeds.end(), _feedQueueSortByLastUpdate);
  2233.  
  2234.     // this rate limiting thing here is not very smart.
  2235.     // we essentially have a limit of 150 calls per hour per IP/account.
  2236.     // but we don't use that. Instead, we send one rate-limited request
  2237.     // every sixty seconds...
  2238.     // read: http://apiwiki.twitter.com/Rate-limiting
  2239.  
  2240.     for(vector<CTwitterFeed>::iterator it = m_feeds.begin(); it != m_feeds.end(); it++)
  2241.     {
  2242.         bool bRateLimited = (it->m_type != TWFT_SEARCH);
  2243.  
  2244.         if(it->m_type == TWFT_SEARCH || it->m_type == TWFT_USER)
  2245.         {
  2246.             if(it->m_payload.empty())
  2247.             {
  2248.                 continue;
  2249.             }
  2250.         }
  2251.  
  2252.         if(it->m_lastUpdate <= time(NULL) - 60 && (!bRateLimited || m_lastRateLimitedCall < time(NULL) - 30))
  2253.         {
  2254.             CTRFeed *req = new CTRFeed(this, it->m_type, it->m_id);
  2255.             req->Request(it->m_lastId == 0, it->m_lastId, it->m_payload);
  2256.  
  2257.             DEBUG("REQUESTING " + CString(it->m_id) + " [RL = " + CString(bRateLimited) + "]");
  2258.  
  2259.             it->m_lastUpdate = time(NULL);
  2260.  
  2261.             if(bRateLimited) m_lastRateLimitedCall = time(NULL);
  2262.         }
  2263.     }
  2264. }
  2265.  
  2266. time_t CTwitterModule::InterpretRFC3339Time(const CString& sRFC3339)
  2267. {
  2268.     unsigned int year, mon, day, hour, min, tz_h = 0, tz_m = 0;
  2269.     char tz_sign = '+';
  2270.     float sec;
  2271.  
  2272.     if(sscanf(sRFC3339.c_str(), "%u-%u-%uT%u:%u:%f%c%u:%u",
  2273.         &year, &mon, &day, &hour, &min, &sec, &tz_sign, &tz_h, &tz_m) == 9 ||
  2274.         sscanf(sRFC3339.c_str(), "%u-%u-%uT%u:%u:%fZ", &year, &mon, &day, &hour, &min, &sec) == 6)
  2275.     {
  2276.         struct tm in_time; memset(&in_time, 0, sizeof(in_time)); in_time.tm_isdst = -1;
  2277.  
  2278.         in_time.tm_year = year - 1900; in_time.tm_mon = mon - 1; in_time.tm_mday = day;
  2279.         in_time.tm_hour = hour; in_time.tm_min = min; in_time.tm_sec = (int)sec;
  2280.  
  2281.         time_t in_time_tmp = mktime(&in_time);
  2282.  
  2283.         if(in_time_tmp > 0)
  2284.         {
  2285.             time_t gm_time = in_time_tmp - (tz_sign == '-' ? -1 : 1) * (time_t)(tz_h * 3600 + tz_m * 60);
  2286.  
  2287.             time_t now = time(NULL);
  2288.             tm *ptm;
  2289.             ptm = gmtime(&now);
  2290.             time_t now_gmt = mktime(ptm) - (ptm->tm_isdst != 0 ? 3600 : 0);
  2291.             time_t local_gmt_offset = (now - now_gmt);
  2292.  
  2293.             time_t local_time = gm_time + local_gmt_offset;
  2294.  
  2295.             local_time += (time_t)(m_pUser->GetTimezoneOffset() * 60 * 60);
  2296.  
  2297.             return local_time;
  2298.         }
  2299.     }
  2300.  
  2301.     return 0;
  2302. }
  2303.  
  2304. CString CTwitterModule::FormatTweetTime(time_t iTime, bool bAllowFuture)
  2305. {
  2306.     CString sTime = "error";
  2307.     time_t iDelta = time(NULL) - iTime;
  2308.  
  2309.     if(iDelta < 0 && !bAllowFuture)
  2310.     {
  2311.         sTime = "from the future!";
  2312.     }
  2313.     else if(iDelta < 60 && iDelta > 0)
  2314.     {
  2315.         sTime = "less than one minute ago";
  2316.     }
  2317.     else if(iDelta < 120 && iDelta > 0)
  2318.     {
  2319.         sTime = "one minute ago";
  2320.     }
  2321.     else if(iDelta <= 600 && iDelta > 0)
  2322.     {
  2323.         sTime = CString(iDelta / 60) + " minutes ago";
  2324.     }
  2325.     else if(iDelta <= 86400 && iDelta > 0)
  2326.     {
  2327.         sTime = CString::ToTimeStr(iDelta);
  2328.     }
  2329.     else
  2330.     {
  2331.         char szTimeBuf[500];
  2332.         tm *ptm = localtime(&iTime);
  2333.  
  2334.         if(strftime(&szTimeBuf[0], 499, "%a, %m %B %H:%M:%S", ptm) > 0)
  2335.         {
  2336.             sTime = szTimeBuf;
  2337.         }
  2338.     }
  2339.  
  2340.     return sTime;
  2341. }
  2342.  
  2343. static bool _msgQueueSort(const pair<time_t, CString>& a, const pair<time_t, CString>& b)
  2344. {
  2345.     return (a.first < b.first);
  2346. }
  2347.  
  2348. void CTwitterModule::QueueMessage(CTwitterFeed *fFeed, time_t iTime, const CString& sMessage)
  2349. {
  2350.     if(fFeed->m_colors)
  2351.     {
  2352.         CString sColoredMessage;
  2353.         VCString sTokens;
  2354.  
  2355.         sColoredMessage = "\x03\x03"; // reset colors
  2356.         if(!fFeed->m_prefix.empty()) sColoredMessage += fFeed->m_prefix;
  2357.  
  2358.         sMessage.Split(" ", sTokens, false, "", "", false, true);
  2359.  
  2360.         CString sStyle;
  2361.  
  2362.         for(VCString::const_iterator it = sTokens.begin(); it != sTokens.end(); it++)
  2363.         {
  2364.             const CString sOldStyle(sStyle);
  2365.  
  2366.             if(it == sTokens.begin())
  2367.                 sStyle = "user";
  2368.             else if(!it->empty() && (*it)[0] == '#')
  2369.                 sStyle = "hashtag";
  2370.             else if(!it->empty() && (*it)[0] == '@')
  2371.                 sStyle = "@user";
  2372.             else if(it->Left(7) == "http://" || it->Left(8) == "https://")
  2373.                 sStyle = "link";
  2374.             else
  2375.                 sStyle = "text";
  2376.  
  2377.             if(sStyle != sOldStyle)
  2378.             {
  2379.                 if(!sOldStyle.empty())
  2380.                 {
  2381.                     sColoredMessage += m_styles[sOldStyle].GetEndCode();
  2382.                     sColoredMessage += " ";
  2383.                 }
  2384.                 sColoredMessage += m_styles[sStyle].GetStartCode();
  2385.                 sColoredMessage += *it;
  2386.             }
  2387.             else
  2388.             {
  2389.                 sColoredMessage += " " + *it;
  2390.             }
  2391.         }
  2392.  
  2393.         if(!sStyle.empty())
  2394.         {
  2395.             sColoredMessage += m_styles[sStyle].GetEndCode();
  2396.         }
  2397.  
  2398.         sColoredMessage += " " + m_styles["timestamp"].GetStartCode() + "[" + FormatTweetTime(iTime) + "]";
  2399.  
  2400.         m_msgQueue.push_back(pair<time_t, CString>(iTime, "PRIVMSG " + fFeed->m_target + " :" +  sColoredMessage));
  2401.     }
  2402.     else
  2403.     {
  2404.         m_msgQueue.push_back(pair<time_t, CString>(iTime, "PRIVMSG " + fFeed->m_target + " :" +  sMessage));
  2405.     }
  2406.  
  2407.     sort(m_msgQueue.begin(), m_msgQueue.end(), _msgQueueSort);
  2408. }
  2409.  
  2410. CTwitterModule::~CTwitterModule()
  2411. {
  2412. }
  2413.  
  2414. /************************************************************************/
  2415. /* ZNC export definition                                                */
  2416. /************************************************************************/
  2417.  
  2418. MODULEDEFS(CTwitterModule, "Implements a simple twitter client.")
Add Comment
Please, Sign In to add comment