├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── dist ├── classes │ ├── API.js │ ├── Facebook.js │ ├── Google.js │ ├── Instagram.js │ └── Twitter.js └── index.js ├── gulpfile.js ├── package.json ├── src ├── classes │ ├── API.js │ ├── Facebook.js │ ├── Google.js │ ├── Instagram.js │ └── Twitter.js └── index.js └── test └── test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | test/ -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | extends: "sourcetoad" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ./src/env.js 2 | ./dist/env.js 3 | node_modules/ 4 | test/env.js 5 | .DS_Store 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | .travis.yml 3 | test/ 4 | .eslintrc 5 | .eslintignore 6 | gulpfile.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | env: 5 | global: 6 | - NODE_ENV=testing 7 | - secure: MCyhbusGrVXad80XC4H40BTHFUlKII4W7+hidIfN+QGkG3xsSIWCMHXIPiUJf9QxTsWq6SfKjPHcGwmtzmUDYJV4OEfP8nuQLuJBdiklKWsO2GXO78KYntT1WVw43fXvfKcXPB3rdny9szvUu+KJip7HZ2YNaMAO/M32alsQXQGX76SKx6XBW5UUZtzcxlK5za1L71pEyF+7thZi7CrelvYYTBKiBOoFa98rhzz6TgfjlKRJmNIryyVpvsptjuqLXrukMmCOwgm/kMMf1vEO6Opf8cAhXQkK5+SQpIBmOw1eeynGASfRlg7OdGlkZAkpjcCy4aDCZI5f41QrcyLuxy7oR8NwX0k0dgrPk6Ld5zB5os0qNglOYaKlT06m2+eEcUImaHRit4fAAGiiothun8HZ1GANV0Sa7UKwaJQzlKm9vleKZFOKvhzPxZ8TDsJOsmGzt61EBIajfsyAlfcAdiyWbud5UazB3j4N8cuPyZVfbEk5CepYgpDb9PnngXGUPicf9cTDTzOeU+k/nQbYKF56AbdhAGYSBD0hkcG6qUmPAORve6COPbVQh+1291zcbr//PV+82fSwiMV+ftNKVzGjOx7TqQrI+/ZT75+KRiSyw4VEeEpOUHE+ixB5NdEenqtGBC7sNoivmrbc1K8b47jGeh8Qs1LMSEksdkmMQ+E= 8 | - secure: buHEPNshTgxUa5pY5X9H2Mod3dV1LhAtZvZhLSPXxF7JFPnghqybDazawbpfLJy2myFerwMf90urdtUwc6AztF0d8j8lPZUfaE+oHmEoSfk1Hkfz6mbUfR1H9xg4dhEMZSdeO9L+J9LnE97tnoRBGNSgp35kxRxLF1hhxFHPdG+wM+Qy3KBlQhmyn7Jm2jEtot4L90x7ovQXIZp7P3RVXaVLNzMzQE6zb1gFVFORPEvy5co7GsSVi2Y8rokJmybsG7galbuTZHcqYxS577Ov/PzjNThOKyWBECAnD9zzNVHWkVRPTzGZ9TLo7HnIhIzlfkfWCaTcwiFHxGSe+eH7qS/B0hzzRB3QZlG9KJNHnmUlV/glm0R+YqLsvlgzrkIomJWVOQd+E9l57P3kJeuC7FEMS1U0Wq6ZSy99FdkP0RIAtKZ5qQjGq7Z9MB58zgS4lOxbsqndQdB6R9HEUfgeB7fsKBiYL36jrOROkq0fQmwFPnhgeOewsErkzletZI6MDPJoTr3We3STSnmTaffMRISyuOHfIFMGinrli30GA1bqtHPnre4ZIO2haQL2PuaLtjkF+h9X9b+EhDLCOClubGkRK1gxZQyJFl2pi/84q4JxJKxDiEBEMAmUYq9c91AJV3IiKzC2tvNIXvI4uPvm4ERNDTe68OjaN6K6Vi9BnT8= 9 | - secure: C95sIZg0mSMzSZfOstA5+fq1n6HAsXacvPLbXfXe9bY08V8KCA47vRKi6LzJlT9Suv2Uofs2MmeDCjqVd5nuvw+/Pf7de1r3WqT5NzkBZz5wB6vX9gWSkZfcyrd7y9UkEzOvuD2TW7GmPX67qVl1ukqSuQaaejNO/vdtldMBnF60CN5XY4vH8HeSIpsYLmW5VQcZtWtkQvbusf7RrlIkaVZ2t0gbP2H/u3uXL5nJxNskAhU+D33tzl6f1FvgpQkoRFTrZ88jWUBmE7g/WaKeNgJVJ+9U3+G4gSjOl+ntSM1xKpjbPLD88NOSJgsqzflBMrhX5EDwzdPb4xk+naaPZY+C03gJC0+rQubTb4lkgOgC3zOF/sIKNd8EIkn4HwJXxSUGh74UhB3Le77t40MKaTk9xpKA0ju4actvQqMUlMzuZK9tc0EOC9GRfLyvz06J0Gcu2LoCf+mhw1eBzgW1qL/qkmgC2krjwueEOiBSKbz4/5cYufOrRc2Bu1IAAnKd99Zp08NiCASDbr1rv9ggeY0bNYcOn+Vjtx6Xzoszidi7gLXUiHCl2Kx1Dav0LWGxdhzkKyyqyR62+wzQ1CgEW5SajHWU23lNbnZ0HM8p5djD7sdnB24q/rgu+5QvgbX+5ONlmeZPKfl3Upgo67xJitbX+w2i2zKXHeU5nhvNA/s= 10 | - secure: Dy0kzfUN6OrGf0A78hDNjrcJLKwD2yVNgWYsMlamEz2b116QwD/Ltlnqd+2nBJhiaRUuR7807ZvEBf5czDXhYCqKWS/N9MHAj6uRJuu4nat9jn2nn9ZXgwcKBFXvSECWk7LR4yaoZ+iphhqykiPyssQaKVNTAf40KM6PlgQQMvlQ2Tsgszo9sInc2ElSSkIR90R+6z80uFT35ETtqJ3CX0XkSKEwjvRINTHR8BKpmSVyQAq/KhbkHpom9KT6UexoNtqZFjihjSY49C9fvqxpiq8oI9apFrd9XvQbzLci57aM30p3Z9XfYmMT2Q4dGE5wcXvXe8VjWhi/S0m+WNO1sIWPXRegwkHvUvy82CN+wDV2u2zirSYypztIQBnwKbRDf4o2+aCnm1P6e9VRn88ISX1LGQ/4bLheClFM3vJw/Db+HUC18PaEwEOdQbHGr1j7vcxitE9BoYu/eaR6/GteMTa3loqn5XHZgTPDFJuhUAtYRV4X/XCfC7hPW8GZ1gnGtpOR9pSZtXmduQ6ziZcZO3nR3tbsJYDEdB06B8nFcZrm1dzEJFTUaDzwTsGJGp22C0Zfb423wFSHrc3ZGoJTAay9zXgrLgWTsXvGgrFb/31JyjfnmROcAQ8ikEZ+f+mUH07EFyANNsI95rXDIbfeIzUbdKAhGhS6UF2LQqG9egY= 11 | - secure: XgiGOs/B35GEHHxSxs6XHDsM773yWK+MOo8cFHwgfii7P7A2kMcuQpzSo91KiwJDMPOZgtcCVo8RZsQWMgvnxCLkV+jtN6/1ejr9ORvx3+ymHrwbo6ZZUYT8YEZaz3jbVCKs3LUMX5TUJZB+KkHsopJxFuybQAoR0TsCwxrXtvdJkiHz1LMHKVnLC6QOLLzMrU4tGp6c3R9/SDimFKo0ubZbqW2cSvR8489+OLOg7kcjwFGOe5qCU+aLmh76aIknVJNhkiObF+n91ngRDwu6sqtR8SCY6nTC9pp0lq1f8bKbaDSMFxeLj4ZGw321F/xgTd6Fq5ZDbzwaXN3jNHXI/UdHBun2lv2GrOCIxlDgza1jursmPQGJAyLW3ZVsN3vPWT/ufjWXrNxL3FdHKN2j3dPwZGxnd1M+QMEHTs/hrKEu1ZMToPndpS1YdUfAnLx79KYnBi7o5dNf037hdtzv5wnbGpivEpbt0KEnmPnICGw3jK2XLCzyGZiLZtcWe2bhpIhS7fnz3XfIYRa3mA2AtQwiqiPm0Bdd53UrRP5ALXrciKx8BaWaT0us0615UQLD1OmehswcPZzeYL34DeL5CuUR0JtOXvVGF6PGCwngi342wq9vZAEbJlqPcO9IfHKvqloFlEaD1IvxEFExuGu9GQvcvwXOJi4pT/JbJ9mInXs= 12 | - secure: cKJIKEHr1ow8tyA9ve+fOj0TG9bXOX3YRMDdTLPr9LwGUrEGkrnnj23fcMo31rOZkYvkbSZbZHMbQLgERQrPcbf64hWgvaI9peYjDEG/R5lFYgvMiN2xEyqSZmYzxs/jGLhr8Cu1tYalkbcFW6XiJX+WKAvpcWwXvm5NmF9IT91/ht5rOpQeetR5DhIyTQcluYI2AuFcG5GJOKJ4bbKErivOUa9qAkd65u1JVsPHdt71dT68mbol36KYk2D8WDTcbiephzSdWAzJuuq4Wq9O3tvkbzXri+qFHkUMpG+2j43pS9YlnAMSYMHQlLXUpy0Um0qT8OcptWWMeW6EFHLl0mWvJcZM7sdybV9X2Au5+VDrLgskLn9zIaMuFJK9AKkWPeiN+K28uFOO0yLKLcObCBWLLqulcTdmfH9M1Y9Y1dcLa5KY+G+9KpV9aIWLx+uh/1SYn2CyBHquD/AGOHzdKYKOgJhYbvbMxnL+dYVtjl8VZ906yioM1xmeKVVhfO+0QQGN6GefQFUJRdg2WiVvZ32WNdDMZ0euH8kpcu8H7jC7UU0KVWx1M4yvBwVlNeql8vIjxFCbV+UT5e4iX+/+ztxKRdpzLpIHFoNKwd4aZ5fgtKxQdRgm6tVE6cwkEjOyoGUEapQz4i5Rej9AsI7agm3qI6w6VBYO3RaXGc3vWi0= 13 | - secure: gXaDISFWwLlkADeV5IPhGDy546Lg7oPw+EGYNf7EbNAsm80fHu1OJXpIvAGEKNropaoiL8+lJ7OGuXUKUJ7i7HA/5qTrULr0De9ewdULGguceivaW4CRzIzC8kW2Dg5kIQklIgFFEeVfSHrP6Vfefa2uY5QPUYirbQUJJepc8mlavEynDLtO8k486F//wQeqO50S0oUFdjZ/Y20bDQxBh2yujS1tXhw7/JCf9/1VSQSK8Cpq0RcWvRGgS4oGoOZsaEeBvknULYo9VlfKBxg9EaklqvCDeF1/y1QDuWt53n1X84JkAo4iw1CnctJ79FzCVoDjEJx5x00RbEInAOn30bNtCpeGQklWC7/lbTLd2v8Jk3B2kPUAyLXrI1WFuMsMAA3Vc/EoLF0Bl8vRkUPv1Lj9xJvIWZfakS6iVIvDy8i/Xe2Bb9PdnbTPPH4Mn4tiUfSuEk6XzRzR5Vp4n3H1AL0mjWHUIneW2QOnQrzeCeXwIzgz6qy5xDkV9mW34VNEJoJBuT1jNA7SdmBitvnFgTIAeHMqUfjwVXus2IfuVG3NmcdE67sHY9qG2pQ47rgGkp3m6BOb7Q9a7c9uVXVkAVfLKRjoosEwFhxXHqDiy1plOGpbE0MvcjzDsBy8+w52+b/Yl5SKfC2U33q8ohmfrpy/k3krERdQJWvqIFdpm08= 14 | - secure: mdRr0/gNHnLiXn1JTWp8V/7rRDm+khEAzdJwNqRoHMuYoivstw36ZHGTbBRJHn+qmdrSEsaOtcrrvBAC370c7PaFRT2NSLNg61gCXWYirZkonQlJyk9L/KZg1+4G1PmnmkLBlVRb2T78mNgAFPrkNNakSNkGXkV4RkoPlWlUDaFRZeWoAo3Zjw1jQYnB7bO15v/zxoK+AkgmLeCW+b2i6P68yOYxztR6SMDFWwfV8NnifQAHXiosa5/J74eejUzk9Aa97tpTxwDFFIuegmEg0ahiCe4udyCwg+6c60nhx81Yfww/cXVJQ2sW5RzDe5XYPSPWl/yWj2YlVF60C/aN+huSeFSGwFDz/5BlfPIq7pHnE1GHh9fYX0mLFJ9b5SQPlSUEiEK9cWUXiWTj7WfQ0XeLFbVLN7y3yvveRBLOkMaKYGychAZJolUhsDKGBj1GdVZWUN08kDXFa3PiKVokAEiezuc5l/F7DaPLO7NqgEamEZUyb2FoPq3rsRV9Gg60TtyzkjkO4K7UD7c6Sj4gfGbOGps1D1G8K0ds7tkmcQYEaqPaxTLIzdtQsWsgOX3spbXdE42fCIMWB+PnHnNs4XpLwAIw7ZpDMz+HRg934kijXK+ET+xp+vCCC9X9gD/8d/oGDUA/XsiCfH4kx0JMB7Up/9G/znP0n6NCj5WMoig= 15 | - secure: o3GAtc6vcVo9kHaX+AcPPApZWw8YBRTQm5SeTEBHyN7maPlUUxLoXid6YzVhxiVI9L3ys/I7x68BFgmPuHkZzwrqpFua4iakB90BcG26axSrGomeEt1nJBTaH++Kd9tve2q7roVKKwtkBoHyGgWQxmHUr2gcTMXCDs2bF7Oqi4prtCWWP1jc/eiUcspEYtRSKAqsBdRhht5Wquj3Th6aL0SKUipR8/CSCBhKrC+1+Xsk52CfR55HJfQGuYl9Z4pGJ3sBJKj2dhSYKFqD/bQHvyCTlDblqlDoMJBJyBLvx0hkU+AHJ2kF8fHnH37WAbeIJHl6imuRKoLan6YlfXBLWjwziHCQdoIB5+85Dm6rmJATZ51YCn7gnOW9x47sdJIMxL5jAEA4IkVQBypCkXbPqDU8FWTbXerSgd5mdwgIbKTxaAH+HRPiFPhadv5m7jVimncQevsDVoCSFhRO9SvZ5qlKKtUmcVdiHI+RE4lrqNjZrJji1Q9otAcClgPm6Lcf3QIIwS3K60cCNvJrgP+nw8anwdx3S7U4/OFXRxHUa69g9V0R60BhRU3XMhGyXuh7bdAEA95NxECjfB3d4lvGGVbb5fnckKTt8owfhV3sZ7oD6a9hzcDWVDaGauba7RzP+P+SH3Z3g9m4GZyf+V/0y+BfuO4gMyviXNRF3CKRlfM= 16 | - secure: UXwy7sIgAmeSfw10b/1015pziaF7M91uO62TAulWqzkLnJJ6JECCj44yTSy3cbevQUEdzocXpTQv1pSe1X2rrdncYDVXGACTDjCqHyv5frGVb0PIUzQkvc4AJpcBAFsSUMNOQzsuctf/UDykqqUtb4Y9HyRbP60Jo542E8oK6v653aO+uT3vzsaF12o1bB5Wf/16pSVYIfNB8/WaKDGpQAFMYFwEGGvMQJYPXqgWIYNPdn9gMPqddhTOXThxLrx+EbmuREoX8xoP5WfpHKnTXICe9UJeHC6pD+I274iSZxQGT19kkiJ3/s0a6mBi+wQUe6SH+ibOzyow0d4LPdXU/kmaoWyuf9bgP0YolzPgsNwIHcU25njbuDx1b8TSFAWgivlWEDT/O80aeYOD3xuXN4NAAYjmAcU//GODUiacuRyFWPa8Kc6TAOkF0ePXvyfMbF4BjYFfdBFNAsM755HgGiJxayJ4fneMwOUhRqob3nJQhjwhaDlUo5YIzk85jNSUZbYGMuRIesH7JMQQZQWMXYy9xFf4dqKk+YGDNfUuzE2xFybt+kD70UYuST83PhItFvLIVwKssjSisjJ5phJVNzA2UDxnB9hZNO0ZU+cESuwG5gonL+f1c8Ls+KGeMsgVHVh1FxkzkzfTwT9ollZhZZu1P1QaY/fyBi1k6Qq5Pjc= 17 | - secure: RzH+Rj1EBkzhTpXmkvh/Xl8VqPDI9NU1LGQgyQyiXylV6D69+popTzmdOEpB7p7QSDEiyTFZxAKft3/Bn+UW0PZJRCb7uR5/LQA3N71Wb0aOZ6TyRo4hwZaFa+lp2c+CeQLyHZlTQGFTZxrYZlrfetNWXalcMrfLE2QVDeOcV9tize+hOoMM9ZRO8LMN6HxnXd7ytmbsFTkH8jeLlWHpxn7oS9a6w9XMwgh7TpG7FRWjoI8bpH5FSI5RpjtytcKrP3+88UkN340D/V/NqXXbJn8USeEuIQjk5Uv/9GjJf8kqYrH15iU23iciWgsgJCgLP0K2m9jM6hQubgAasTM1EyG2Wqwn24ROBsEHLekR3OILdKikFqmXe/gD8IMLrj73RbpRFkieO/AtUqG89WFP6hSRYU/siaz5u2ErQQyy3m9i2CjXJx0JuOTUIvqcQOgnNcQc9EPAu/KkFfKpT6U4L91vK0aYMY9txUXWzjDmf58NR4FkgmxKc4hVM94I1o41xB8A97sINrow2A1CE+93Nk/qeuO1T0sPxF3lL4TkPOwVimmXIrTYPwuKvCQYECik8dDT4/is6GJB4aoi9nlEFnTaJEy1gfTLlHULI6tI7j7Jvp1t+/WRC1nQCUQEq0RilL2weR5eXRE1pkFjGko1xiflNELlQuYwGlFtkrUTF4Y= 18 | - secure: AIbqvGJjpq/f1b1jd+RS8FzxkImGHZgU9GegIIrc5t2IYqBR3Ss/ngkcV9bQkT61wDUWfOYXArNhrvhTta6bqLi1406uf9RCHKNPL11qHAvjemKGbuj2cwa+cAC9xSP+3XhldKm83zpj+IYqxEM8JQsl5epAacdv1ts3yhVHk4yHwfNGiK07MxLa4HRU3A03DxkNXUu4KT8TXZhCyqMoMC/EY9riXIDM2vKb/bnqLxomovGK2OTeMYfz0Mr1QAVq8o+ZLjxzVr6AVKLGERvJJw6AICV/PmIq9ejC2/S/iRYMrnVmN7mL7AlIaBhWxB0ucoXbgceX5JsVqEU4IpU2IftSaweDQYLNesuzH5kevm0qkArgrjpQ8joUsrRYOS4jPWVhuTy2aRdNUuuxZken0mGG7dh8v/OFR+Fc3StMsJRvOoTjJLoG5azcdW0Zx4mRUqgltkHp9sgS7tGK+XwjjbzAA0F6tzLZa6IGKelToumINBuPcduIXCy2jiPlP02rFA1sa12ZURAFVDh3ds3j8Mgu2xQefcGyL0i4f3HX30dDxsC6lBLClwINsYOrP8GXbbM3/6zKEfUXo3KuaEEDEFq1a84Wt5/AQ+MMzF6nSDHOzLTf4CJMfLdAAWgNID+GcJH3zbjRLj5Q6w82t61Ng9rrhXRKzcp734nBlc7B2L0= 19 | - secure: epT16wu0fc9agp1HqhHIfrI9GH5RgxbpaySdt25hjZ55ZCICc4ActygPA8Xd4yfk9b45cYjQHvBhiPKfpXtVJ/76EVuyBcFgqjgNcN0I5dcEytpevWE4PEBYUJ3ZJjjsm5Nxnoo0zoMW6LybYTabVH51AfqL9tTviJOTRxqv2uyvZlakaLLX5xidKrVnWX3AMd/w5ItFxdJLwB92vCVa/Ra7yItrTFAmh/d8jHKoeHfUOxJ+Z7CFMteQJvqabZcIQMsRmqQ+0YyHPuhupfgiiDb/zGT8fjRt/mEJfcv7GVKgDLH5iY+LERStdSAvFKRIFdyzHCdWI/Zo7tos7x4RwHYd2Rw5DPbJCcNvUU/WxNHu1kqWY5UXYzB9UNvwz21Y/sULrH0kjYfQUQicMvwY5d1GlPov4iHu1L2bSdwwpSDW3mWc5zBFPkyH4T1e09jteli4YPPv9Vf5DPE5j2MFG11K096ixXoJu042Ts7ScrVZtec6Cfzj83mi9/RJW9my7N5EZwongqEXl2u+H4INH8XX2gfgH7zubkBAr0hrDjIX1OQC1K4opezQ5x/7AUDTkvH5B+e5XDozp6mPthTpOvR/WwVjcQy7gHJsq9bLopIMm6YBHoF/5GAFK/kW86RlSfYaPFPLwnIQjjXO3CG3sKMI4N/BHR2HyeJq0jCQ7LU= 20 | - secure: bpqLOWO7KHa8tK4KyiySKvZJtJhete8XMcObe0HKj2+gKUC3J6UaNAF2quLyiasEHDkFJWdvneCfszjIDRD2iD5s+smLEhZC6eGnKbLwp5cIj7sD61wOdftQhTRnjIoAizJejsP6YewuEKnJy+pipZ4Il4b7+tYq/ywX/66veefH9toW3hqhuYixRTnfEWogUGnUpCMrwwTFrCV8u0Rj2rpkL6W2FhJXsLaFca5CCHXRzF/IgwCvxJsPUNlNV4nH6j9eIWUixXzcWIQTy6yv/EPMPULv5GsWKee2+/4ssWzkmxzxhLkyuJIRbGAQiH7nYjIGqXXkYLRXXe7q/i9tCBBEEg7E0IwC6y9uIlc7t8f1EdkAWtazszGnF+NMSz//JEPRwI3zaSJ8xjzkna5cMu4vhd5pgWeuRhCAHNcC6pgEqzQ4gQp096sNIHRXz0jV4XwxmqNaMuW2Wrfk2hJ2dvoxoBhiTLf8Zb8qeFOmvxbctYiSN+dgf1QCq/4ZfrRuvi5xmazffeFHQX9dMFwkN2XH4rI/FaTBUucek90I4x0mQAvG9x8FbiyZTnphoa9L6fpB01GKxEbRTKaKiUM5XW5r76FCkc6ValB/MVG62jAlBF9d9rScwbRNA/TLxTuygXcxcgNujwfhk7Y8M7LXWpCgotkSM2Tka/LGzBdCK4c= 21 | - secure: cDjKLEhfTMhdpSmEOZMaVOQEDAOzYlGzmcqVFPN4il3ax5zSA9bE0DHOSr0HHyFH1tePcuiHdUl5jO7Hgygb8Qodqs8U5fhg55n2FzQzKlUdq2N4dPibVqJffq5o9ih4Dfaqabb+wEVmXRZFTHHmP6spNvHeokG+hknnRkOaoiJqwiXpKXpjfQtYFUISttXt1rSszov0Iezf9Vwh1YG/4z/XvuTdopYrQXB4EHEPxb3YAlRK5jNpbVpT4cfFudYYGx1fZNpzHyEKqGWQQ5VcxyQpnsy80u5Q1CJ7HLPr0Q39ZNxldLXGT2tAkT0zO3RChvUSGJJYhK7m+eYgtVVeQkkzaaoZDRNbiANtGBc5aLqOBo3eVm+XBPaqFp85rBlJkbf8KZZHmVZrO2qXKEgkC2YufOTooXH68DYFQQJ5qfuKUg9QG5w+F4Ci1cQGjTziBQLspNLVZtbyJJU1QDXkbKgaHFwLeH2xNZd/IUfBeb+YTh05q2ThRduKTx9ih93ihQv8yiRS8kcjBbbdORMPIqFNag7R299d1xoK6nSUxSLxTKIclVFXLW8uDuv4S69jBZZLGP+Mk08/IeUc92thlIDlV/yC0F2xEs9S/HGA6OJdrQX8pLEd0d6S5apqHnciSsCI0FPVKVwZnfpUNDlFXGyCi45k6ORpf68i4Ab7lsc= 22 | - secure: lUewS/NCk7+Fg3B9fn+5n0ASY7gxIiqo/58ns/jGXYvLL93Ip6+QTPRxW5L5kLUHPYbQXUQe2zliwcBaEVYPnJ373zGev3uqf/3ureutYogHinyDwrAIEORSWTnP6czwmAUBM0Om+oek3zIVax8X9yZjnk9er51bi4pI/4gRnAOB/WWrxb1jmAAIECtyLisABUVwE/2qcfneKN/8FGQgujlH/MSUFVoVMfSw76FEFDgFFWcdkCFDTqF8yDwy5MRToxMw0hE7vt9iz6kTSUYPajesZGeai/Ythw5St8HG/Bv3im/hkdrK3gdsqTAD0iUYnq1U+Fd8lC/Vzmr2CbECgMlJ3zWsQTjo/cUORl+uloTK7qRTYkQ9rK6Fg/PCu2jqmOxFcdFotTab4/88pUdW1OhBAZKX8N6iCKdpl+L8Ptf/UD5GOOOYTybDn2h8/nWFNt/GH9SeKNFFmK0eFunNvBet/5xbI7+rhsXxa7EuVNQSP2N/CyALzv0oRcJUK6AqZYH+oWF/oqMleleUSpUkb1oKm11WGnaaSJLxoLRAcDCSvphvSfeLolXcHSDa61L+6O93l8p+rkGjL5pUlgui/0qh/1bi3Wo36xUsj0uvvmHHBVVSjx40Wm8iCvkp2PEV+vcAKKViGQLkNxnqebYiJNKJOPsvQwJcBsVNd2MX6QM= 23 | - secure: fHO0VujaCQ8VYulXPVAxYMct9v29ZYRJhTyMUlEn8xR2EdachseougYJkaWiTk561+7lCsvgI5XUsTCzitHHtlHxfILwO1s2b1zz1CYH+gq4N20zYlyNn0jNoJfZDbU/K4EItVP2RPbJaj5kmcsHzfWdtu+VYiHbZPF09bnJVgBvoSdgHmLbY1EJfubBiKWCRDwZGYvZT/NoWHA/w6A4l/cw31yc/Ug8BfIW57RXaQ6SdLQCqHTRPGgGcshdyfBeoXTpHdbuLRo5+RMDjf+PbJ5q252IrEzZ6BA8mnR0VEzoHSg4bhuC41+3qch+bxGKAqja3RvL3yj/K+DDfolUXy5aY0SJhKVXZpI7Rl13MoJgI14oZhvb+FZbbcBUxm4vURZvGLqqGd+JoGskzE6MqDI1dOlxctEwhjOtD01BiHbSQ8EiO1SrlyycGV+H7fMVm9xILx9YJzNJXVzhDKBgbBsiSN+d4zdWqzbB9cBZZ/7oM6JJqJPN/pMCJAMZ3DZ567Zh5oVY7GU7HVes7lPchNwtPuzsheTEJeZwfPoyWcjBhnSy1AZVVjU+TqyziKDSNpe6NzzQKR3E3YgVjiyAJn2pp6jMOIWabH14NGTmvxL6Oysjloh4xXUgRdGFrdySSL6i/mf5KRhOz2LVudCww2aUQDwsWLTUraj+vx2GEOY= 24 | - secure: NUSphQ5+GIJsYeky+tLP5mZJnwU4fD0+F7SQ9k7LFBS21hHhuuA5z/xWg52HKye2sYm7lOLgVjSExLsXrDPfXPdl29obvp9Ugg64XzHUEruuupWE2kDUEARJGDchbz/slzaSHJMo1elKc0iHwWikWnjXuHoGfn1IRfCgPkylD+wo1HcpELk87OwnsD7jlRYprfr9TcuZ2u4aqzTwkTmiRh4ExACwxNyPzdf9RsZNPJyNDV09ka7/IxMO7g+ns8rl4OBuoXRYor53XuQWFi9TodI9hrVmz0FDpNjzsw9cjX59lW3+fpbunYppee7AP8AtoeKLQTjT9kc0Pf+YCpUhWHFvngQ7wnyV0zCUU0eY/Ygb+Te4FUU1ZjEboFfo9lmkmgX7+o2J8IOKy/o9m5iXSSijFKOJDGMerwpRIhzTJj+h1YPVbBFPo4E+kWlISmCbamko321jm99XWWY+d/WVhZ91Y1oVHYsPgAVjQjWHYWOaNdl37sLr4WFoka+3A4t1kpGghCGWq3x61N9tgA2b5SECnFm33HmGlOvcp9h37VYwcozt+v0XOsKFm5fPBknRKWoqT/ilS6GQASq7WahlAQxWMOkTKrxQ7zwgtpz6234V/LiTFs0rHOxDQojtnuntIbTuTl6uBh8fFwoBaVb5buMhKSDwq8dExowOL7neWD0= 25 | - secure: LazGIK69ebJ1JDD6HYOxHZHIH0SjkfpYtc+PubIIc0feiMyvlLn+/8h7wRvzr70w80RRQUvyqcFC9Y/Er10y7nnybhX/NsZpbtCcfbJrLV+v0HVtpBKL9ANDYRr+S1iLi3C/hIxfzuIQdnVEnwLRJDtOIXTE/7iN2yiN9JYtjTZrgG87se+QSDplL5D/3PeCE4xJ9oyrRdIvArSiAzAYpcbaYHxt3osAuCjz8HXHY5gh64UjthV1I9mqyRLHosGB9jnA93s/SwAAbfL7SHqM3sWJEWyTxR1680ZUeMywWKmIo3RwWoXb356gQTSm2R+5VbxtN/sb6pYoInWaV31hzlZLF3ag7TLQ8kD0vNpVVT1ENOUl5pYIYmZeyM5/5t9hEgCXnJpFW9IuXgKvnRpPt7GtXPNaRBwH/BJQS+K7gO87unXXNdzW58AUQKLK0WXThfYQGHXgY8iOVvFOmCeNJ7D3PvqbXn+y2o0Dz3adJgTKL41HxKJe2CMCGqzQCZssSq6SZudfpVyU3LG1pwAE1P/tf6jr1m8oLzx3pKhpRE246YFlTOQQ4k3xEkNObr6NOY6Jbpyo2ltaFzmN4pxR2ZMUSHo1QsAem/wJDhKJ8ZtZKmwelUlB8UW6s/GicezgfBO62NDVSWdOlZoVtTcux3fNwbGy1iloP+gyGvU42K0= 26 | - secure: MNR57F6Jk/CJXDgUfaR6LlDXUJrcl0C2U5NHs81Kw6Da427QE1B08ntaHSrAIv+dA7tm6Rm/1toNjdwTYnxILPBdexBQYQbDmmogvhQzcNdRv8K2W/OOpV6BuNP9+fAkw4PBicsQU4859iqjpyHjp4xlsdnAdR8iVdV7BwnAhnjk37y8X8xRH3iB05oddCdChnioTgQ/eKXzs1GA4cbvMkaL7BJ7ExaX4Whu8WmJQtEUSycI90sZ32iROtvBawXuujRB/AvUOIQHhLVLCvorpUt9tYwee06gwtI8K8IcUDwY4oNPsddYFYJwLNBvAnL1/XO6Qccab+zhEKylT7mmOAAUc69CaEf6Clcj+M3vbApRlLuSScLNfgtpl8S/5HJD1SxwptEPeYKTl2gYO2lm3c1GTJTwh202oRAi7F4OiN00htK1lYCCv3WePi4CfVsUxWFzEE7Zl5YCyj4WwcTlqbGtDebY2e70TMZbiB7+N4gKT9Yyu8n3saDjaj57Wthn8RaT1inuIs14eApEnBJGCTEts6Acyefg1ACDY7JRQ0Q1ikIJkWpM4WjKJHN1pgTb762079C/2batKRHRH7QrW6uBAqMVwXZOCua+zKx67nqrbas7LPC507PL5WUKLxjdyQt5OE1DAtjhGP7T4/YrCe6RFjJUcESN7l+y6PziWXo= 27 | - secure: R9ZZ4uVVymRxTmrin7TAshQAR9TtGgfo0EcySA86HlUMq5MXCX6C50Q8G/51ZMFW3+s9bZSWKKVfuqZE95qjXPfsJiAhzqIcrLMpLbCGAhRV3e+oU7GQinkx+RDalwCWi2gj6gG/VvsG0rn9X9DBvFMpfHjX6BgjkprS/EPW2VorkrffWhdZJEmiopi4yXAUwi4rrVmifB/bq0bRbX+OgqL3JALQYPu3OUklvFBf+dybX/aQxNRUA3lANBpWlGGQ+OqnbGmHE+A8fQJTvmq6RZjpz38tte+beZOnANxLnBCitN3bEjxv1LI7x4bmBLTON8vlbqy9zuLGdoZ0WGf0ZxFcqDteUkZ7a8ZqjfvOKMQKNYBL2TWnyLhWhyUNChIAdMac30ZEGE95BVOT2S4sXyv9FcxqB+KU/J0ESgUCj8pbkBN8GI/yQblnqhKJZYM49EP9Bg/R+45aYLpCqsEbFnYot6OcHXTGIwtXUvs0/qHAZxBs4oy4ZTA0bYaR92/HrDL7XOXy+qKihl3Qnwin3l6nuaCyoiKx12rP3gf7/b7seR2LR1vbUuVSrKw0txbblvzd+YUiE9ALncxiecslpENd8BV5SSr4L8dMyYGlfb5KnaMC1ZqwqkDrQNx6ZOftugqsXu5727GVcZfynnKcZr4JKT/OWxrEvBSV5tFy4hk= 28 | notifications: 29 | slack: 30 | secure: BzyLsmZpJK5n9HT7R8Yc0/K2WAwVeRxcltZF0isEolO02xORq38GByZrS496yrdRpj8Fp7u4Qzk6ednGIagcOOU9d0LLUaOwNUGYbfhOWEj0xHg0t/vKg/l9imGaMlljI4JXDEQgCaa2yvvJwooNkdSPdlVSIzrCefZndbX4SfGOW1MqfjgzWdYTNdW3m2CgLUv4urxftmCOV2QmAonTj8KfO6JPqRTlVeo68tt12jXIX+yYgditp3FZ06aQKkfxOFD3HGMCfDjsZO1T37sPcnj2Qv/Xog1FGvyivGpE6+9yFxbWpd1sEFtMNk4gM7eM0a/YkgTXGNP6t68brdNnX5iOeJqi7dgO402buU92xmxLJsQxFNYS5fhA+1xQWC5Q1GVCKAnVC9q7e4bOv566/lUiYlpPxUxQdeOe1bdtUfq1W9GMbTPcjTD7zCTh4BLqanDD7AUFbDaB1e6qukflJuxqQ78/4ZFvNJi61n0aSoH12D0B/Lwsy1LZhZD2iByXmgb4kEpX5Up/3ubqqxr636B34cmBbbzbbpcibbHPY5PXY4jZ38OST77vqIuThXK/7WPcQ5IdoOyj3eitNHZOacC84TAQJ/sG0OMer/HSB8hD1+Ycz27paHDuSJ6vkaAZePeTX0KXIbYCPwJJdLuDKF7aLL9Ip/T/s/xGjYvPuuU= 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sourcetoad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-social-feed-api 2 | 3 | Simple module to fetch all social feeds and output in one simple API call. 4 | 5 | [![NPM](https://nodei.co/npm/social-feed-api.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/social-feed-api/) 6 | 7 | [![Build Status](https://travis-ci.org/sourcetoad/node-social-feed-api.svg?branch=master)](https://travis-ci.org/sourcetoad/node-social-feed-api) 8 | 9 | ## Currently supported 10 | 11 | - Facebook 12 | - Twitter 13 | - Instagram 14 | - Google Plus 15 | 16 | ## Install 17 | 18 | `npm install --save social-feed-api` 19 | 20 | ## Setup 21 | 22 | Instagram and Google both require user-specific access tokens, thus requiring special setup. See full example below for more specific examples. 23 | 24 | ##### Instagram 25 | 26 | **Before you begin, make sure you have an endpoint set up for your redirect uri. See full example section below.** 27 | 28 | 29 | 1. First we will need to get the user id of the instagram user you would like to get the feed for. To do this you can go to: https://www.instagram.com/YOUR_USERNAME/?__a=1 30 | 2. In a web browser, navigate to https://www.instagram.com/oauth/authorize/?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code. It will prompt you to login with an instagram account. This is to generate an access token. 31 | 3. Once logged in, click the green authorize button. 32 | 4. In your console, you should see an object output. One of the fields is called access_token. If you see that, you have successfully generated your access token! 33 | 5. Take that access token and place it in your applications env file or equivalent so you do not have to continually generate a new access token (Instagrams access tokens do not expire as of today). 34 | ```javascript 35 | social.initializeInstagram('YOUR_CODE_FROM_CALLBACK_URI') 36 | .then(response => { 37 | console.log(response); 38 | instaAccessToken = response.access_token; 39 | res.status(201).json({ message: 'Access token generated successfully!' }); 40 | }, () => { 41 | res.status(400).json({ message: 'Error occurred generating Instagram access token.' }); 42 | }); 43 | ``` 44 | 45 | ##### Google 46 | **Before you begin, make sure you have an endpoint set up for your redirect uri. See full example section below.** 47 | 48 | 1. In a web browser, navigate to https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=YOUR_REDIRECT_URI&response_type=code&client_id=YOUR_CLIENT_IDscope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.login&access_type=offline 49 | 2. Click "Allow" 50 | 3. In your console, you should now see an object with a field "refresh_token." 51 | 4. Copy that value and place it in your env file or equivalent. 52 | 5. Now we will need to get the user id of the profile you want to get the feed for. In a web browser, navigate to https://www.googleapis.com/plus/v1/people/me?access_token=YOUR_ACCESS_TOKEN. You will see a field called "id." That is your google user id. Place that in your env or equivalent. 53 | ```javascript 54 | social.initializeGoogle(req.query.code) 55 | .then(response => { 56 | console.log(response); 57 | gAccessToken = response.access_token; 58 | res.status(201).json({ message: 'Access token generated successfully!' }); 59 | }, () => { 60 | res.status(400).json({ message: 'Error occurred generating google access token.' }); 61 | }); 62 | ``` 63 | ##### Full 64 | ```javascript 65 | const social = new SocialFeed({ 66 | facebook: { 67 | appId: 'YOUR_FB_APP_ID', 68 | appSecret: 'YOUR_FB_APP_SECRET', 69 | pageId: 'PAGE_ID_YOU_ARE_FETCHING', 70 | image: { 71 | height: 100, 72 | width: 100, 73 | }, 74 | }, 75 | twitter: { 76 | consumerKey: 'YOUR_TWITTER_CONSUMER_KEY', 77 | consumerSecret: 'YOUR_TWITTER_CONSUMER_SECRET', 78 | accessTokenKey: 'YOUR_TWITTER_ACCESS_TOKEN_KEY', 79 | accessTokenSecret: 'YOUR_TWITTER_ACCESS_TOKEN_SECRET', 80 | screenName: 'HANDLE_YOU_ARE_FETCHING', 81 | options: { 82 | excludeReplies: true, 83 | count: 50, 84 | include_rts: false, 85 | }, 86 | }, 87 | instagram: { 88 | clientId: instagramClientId, 89 | clientSecret: instagramClientSecret, 90 | redirectURI: instagramRedirectURI, 91 | userId: instagramUserId, 92 | accessToken: instagramAccessToken, 93 | }, 94 | google: { 95 | clientId: googleClientId, 96 | clientSecret: googleClientSecret, 97 | userId: googleUserId, 98 | redirectURI: googleRedirectURI, 99 | refreshToken: googleRefreshToken, 100 | }, 101 | }); 102 | ``` 103 | 104 | ### Full Example (with Express) 105 | 106 | ```javascript 107 | import express from 'express'; 108 | import bodyParser from 'body-parser'; 109 | import http from 'http'; 110 | import SocialFeed from 'social-feed-api'; 111 | import { 112 | fbAppId, 113 | fbAppSecret, 114 | fbPageId, 115 | twitterConsumerKey, 116 | twitterConsumerSecret, 117 | twitterAccessTokenKey, 118 | twitterAccessTokenSecret, 119 | twitterScreenName, 120 | instagramClientId, 121 | instagramClientSecret, 122 | instagramRedirectURI, 123 | instagramAccessToken, 124 | instagramUserId, 125 | googleClientId, 126 | googleClientSecret, 127 | googleRedirectURI, 128 | googleUserId, 129 | googleRefreshToken, 130 | port, 131 | env, 132 | } from './env'; 133 | 134 | const app = express(); 135 | app.use(bodyParser.json({ type: 'application/*+json' })); 136 | http.createServer(app).listen(port); 137 | 138 | const social = new SocialFeed({ 139 | facebook: { 140 | appId: fbAppId, 141 | appSecret: fbAppSecret, 142 | pageId: fbPageId, 143 | image: { 144 | height: 100, 145 | width: 100, 146 | }, 147 | }, 148 | twitter: { 149 | consumerKey: twitterConsumerKey, 150 | consumerSecret: twitterConsumerSecret, 151 | accessTokenKey: twitterAccessTokenKey, 152 | accessTokenSecret: twitterAccessTokenSecret, 153 | screenName: twitterScreenName, 154 | options: { 155 | excludeReplies: true, 156 | count: 50, 157 | include_rts: false, 158 | }, 159 | }, 160 | instagram: { 161 | clientId: instagramClientId, 162 | clientSecret: instagramClientSecret, 163 | redirectURI: instagramRedirectURI, 164 | userId: instagramUserId, 165 | accessToken: instagramAccessToken, 166 | }, 167 | google: { 168 | clientId: googleClientId, 169 | clientSecret: googleClientSecret, 170 | userId: googleUserId, 171 | redirectURI: googleRedirectURI, 172 | refreshToken: googleRefreshToken, 173 | }, 174 | }); 175 | 176 | let instaAccessToken = instagramAccessToken || ''; 177 | 178 | app.get('/v1/socialFeed', (req, res) => { 179 | const accessTokens = { 180 | instagram: instaAccessToken, 181 | google: gAccessToken, 182 | }; 183 | social.getFeeds(accessTokens) 184 | .then(response => { 185 | res.status(200).json({ response }); 186 | }, () => { 187 | res.status(400).json({ error: 'There was an error fetching feeds' }); 188 | }); 189 | }); 190 | 191 | app.get('/v1/socialFeed', (req, res) => { 192 | social.getFeeds() 193 | .then(response => { 194 | res.status(200).json({ response }); 195 | }, err => { 196 | console.error(err); 197 | res.status(400).json({ error: 'There was an error fetching feeds' }); 198 | }); 199 | }); 200 | 201 | app.get('/v1/googleRedirect', (req, res) => { 202 | if (googleRefreshToken) res.status(400).json({ message: 'Refresh token already generated' }); 203 | if (req.query.code) { 204 | if (!googleRefreshToken) { 205 | social.initializeGoogle(req.query.code) 206 | .then(response => { 207 | console.log(response); 208 | res.status(201).json({ message: 'Google tokens generated successfully!' }); 209 | }, () => { 210 | res.status(400).json({ message: 'Error occurred generating google tokens.' }); 211 | }); 212 | } 213 | } else { 214 | res.status(400).json({ error: 'An error occurred' }); 215 | } 216 | }); 217 | 218 | console.log('STARTING SERVER'); 219 | ``` 220 | -------------------------------------------------------------------------------- /dist/classes/API.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 10 | 11 | var API = function () { 12 | function API() { 13 | _classCallCheck(this, API); 14 | } 15 | 16 | _createClass(API, null, [{ 17 | key: 'normalize', 18 | 19 | /** 20 | * Normalizes data to make it consistent for all social networks 21 | * 22 | * @param {string} network Type of social network (facebook|twitter|instagram|google) 23 | * @param {array} data Data to normalize 24 | */ 25 | value: function normalize(network, data) { 26 | var items = []; 27 | // TODO: make these fields more customizable 28 | // TODO: loop through all attachments 29 | if (network === 'facebook') { 30 | for (var i = 1; i < data.length; i++) { 31 | items.push({ 32 | id: data[i].id, 33 | text: data[i].message, 34 | created_at: data[i].created_time, 35 | media: data[i].attachments && data[i].attachments.data[0] ? { 36 | images: data[i].attachments.data[0].type === 'photo' ? { 37 | standard: data[i].attachments.data[0].url 38 | } : {}, 39 | videos: data[i].attachments.data[0].type === 'video_inline' ? { 40 | standard: data[i].attachments.data[0].url 41 | } : {}, 42 | share: data[i].attachments.data[0].type === 'share' ? { 43 | standard: data[i].attachments.data[0].url 44 | } : {} 45 | } : {} 46 | }); 47 | } 48 | } else if (network === 'twitter') { 49 | for (var _i = 1; _i < data.length; _i++) { 50 | items.push({ 51 | id: data[_i].id, 52 | text: data[_i].text, 53 | created_at: new Date(data[_i].created_at).toISOString(), 54 | media: data[_i].entities.media ? { 55 | images: { 56 | standard: data[_i].entities.media[0].media_url_https 57 | } 58 | } : {} 59 | }); 60 | } 61 | } else if (network === 'instagram') { 62 | for (var _i2 = 1; _i2 < data.length; _i2++) { 63 | items.push({ 64 | id: data[_i2].id, 65 | text: data[_i2].caption ? data[_i2].caption.text : null, 66 | created_at: data[_i2].caption ? new Date(parseFloat(data[_i2].caption.created_time, 10) * 1000).toISOString() : null, 67 | media: { 68 | images: { 69 | low: data[_i2].images.low_resolution.url, 70 | standard: data[_i2].images.standard_resolution.url, 71 | thumbnail: data[_i2].images.thumbnail.url 72 | }, 73 | videos: data[_i2].type === 'video' ? { 74 | low_bandwidth: data[_i2].videos.low_bandwidth.url, 75 | standard: data[_i2].videos.standard_resolution.url, 76 | low: data[_i2].videos.low_resolution.url 77 | } : {} 78 | } 79 | }); 80 | } 81 | } else if (network === 'google') { 82 | for (var _i3 = 1; _i3 < data.length; _i3++) { 83 | items.push({ 84 | id: data[_i3].id, 85 | text: data[_i3].object.content, 86 | created_at: data[_i3].published, 87 | media: { 88 | images: data[_i3].object.attachments[0].objectType === 'photo' ? { 89 | standard: data[_i3].object.attachments[0].url 90 | } : {}, 91 | videos: data[_i3].object.attachments[0].objectType === 'video' ? { 92 | standard: data[_i3].object.attachments[0].url 93 | } : {} 94 | } 95 | }); 96 | } 97 | } 98 | 99 | return { 100 | _meta: { 101 | count: data.length - 1 102 | }, 103 | account: data[0], 104 | items: items 105 | }; 106 | } 107 | }]); 108 | 109 | return API; 110 | }(); 111 | 112 | exports.default = API; -------------------------------------------------------------------------------- /dist/classes/Facebook.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _request = require('request'); 10 | 11 | var _request2 = _interopRequireDefault(_request); 12 | 13 | var _API = require('./API'); 14 | 15 | var _API2 = _interopRequireDefault(_API); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | var Facebook = function () { 22 | /** 23 | * @param {string} appId 24 | * @param {string} appSecret 25 | * @param {string} pageId 26 | */ 27 | function Facebook(appId, appSecret, pageId, profileImage) { 28 | _classCallCheck(this, Facebook); 29 | 30 | this.accessToken = null; 31 | this.data = { 32 | appId: appId || null, 33 | appSecret: appSecret || null, 34 | pageId: pageId || null, 35 | profileImage: profileImage || null 36 | }; 37 | this.profileImageUrl = null; 38 | } 39 | 40 | /** 41 | * Generates an access token from facebook 42 | * 43 | * @return {Promise} 44 | */ 45 | 46 | 47 | _createClass(Facebook, [{ 48 | key: 'getAccessToken', 49 | value: function getAccessToken() { 50 | var _this = this; 51 | 52 | return new Promise(function (fulfill) { 53 | (0, _request2.default)('https://graph.facebook.com/oauth/access_token?grant_type=client_credentials&client_id=' + _this.data.appId + '&client_secret=' + _this.data.appSecret, function (error, response, body) { 54 | if (error) fulfill({ error: error }); 55 | if (response.statusCode === 200) { 56 | _this.accessToken = JSON.parse(body).access_token; 57 | fulfill(); 58 | } 59 | }); 60 | }); 61 | } 62 | 63 | /** 64 | * Fetches feed from facebook 65 | * 66 | * @return {Promise} 67 | */ 68 | 69 | }, { 70 | key: 'getFeed', 71 | value: function getFeed() { 72 | var _this2 = this; 73 | 74 | return new Promise(function (fulfill, reject) { 75 | // If picture is not set, get that first 76 | if (!_this2.profileImageUrl) { 77 | _this2.getProfileImage().then(function (res) { 78 | _this2.profileImageUrl = res; 79 | (0, _request2.default)('https://graph.facebook.com/' + _this2.data.pageId + '/posts?access_token=' + _this2.accessToken + '&fields=attachments,message,created_time,from', function (err, response, body) { 80 | if (err || response.statusCode >= 400) { 81 | reject(err || body); 82 | } else { 83 | var output = JSON.parse(body).data; 84 | output.unshift({ 85 | id: output[0].from.id, 86 | name: output[0].from.name, 87 | profileImage: _this2.profileImageUrl 88 | }); 89 | fulfill(output); 90 | } 91 | }); 92 | }); 93 | } else { 94 | (0, _request2.default)('https://graph.facebook.com/' + _this2.data.pageId + '/posts?access_token=' + _this2.accessToken + '&fields=attachments,message,created_time,from', function (err, response, body) { 95 | if (err || response.statusCode >= 400) { 96 | reject(err || body); 97 | } else { 98 | var output = JSON.parse(body).data; 99 | output.unshift({ 100 | name: output[0].from.name, 101 | profileImage: _this2.profileImageUrl 102 | }); 103 | fulfill(output); 104 | } 105 | }); 106 | } 107 | }); 108 | } 109 | 110 | /** 111 | * Fetches profile picture 112 | */ 113 | 114 | }, { 115 | key: 'getProfileImage', 116 | value: function getProfileImage() { 117 | var _this3 = this; 118 | 119 | return new Promise(function (fulfill, reject) { 120 | (0, _request2.default)('https://graph.facebook.com/' + _this3.data.pageId + '/picture?access_token=' + _this3.accessToken + '&redirect=false&height=' + _this3.data.profileImage.height + '&width=' + _this3.data.profileImage.width, function (err, response, body) { 121 | if (err || response.statusCode >= 400) { 122 | reject(err || body); 123 | } else { 124 | fulfill(JSON.parse(body).data.url); 125 | } 126 | }); 127 | }); 128 | } 129 | 130 | /** 131 | * Method called from server 132 | * 133 | * @return Promise 134 | */ 135 | 136 | }, { 137 | key: 'fetch', 138 | value: function fetch() { 139 | var _this4 = this; 140 | 141 | return new Promise(function (fulfill, reject) { 142 | // If no access token yet, get one 143 | if (_this4.accessToken === null) { 144 | _this4.getAccessToken().then(function () { 145 | return _this4.getFeed(); 146 | }, function (err) { 147 | throw new Error(err); 148 | }).then(function (res) { 149 | fulfill(_API2.default.normalize('facebook', res)); 150 | }, function (err) { 151 | reject({ 152 | source: 'facebook', 153 | error: err 154 | }); 155 | }); 156 | } else { 157 | _this4.getFeed().then(function (res) { 158 | fulfill(_API2.default.normalize('facebook', res)); 159 | }, function (err) { 160 | reject({ 161 | source: 'facebook', 162 | error: err 163 | }); 164 | }); 165 | } 166 | }); 167 | } 168 | }]); 169 | 170 | return Facebook; 171 | }(); 172 | 173 | exports.default = Facebook; -------------------------------------------------------------------------------- /dist/classes/Google.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _request = require('request'); 10 | 11 | var _request2 = _interopRequireDefault(_request); 12 | 13 | var _API = require('./API'); 14 | 15 | var _API2 = _interopRequireDefault(_API); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | var Google = function () { 22 | /** 23 | * @param {string} clientId 24 | * @param {string} clientSecret 25 | * @param {string} userId 26 | * @param {string} redirectURI 27 | */ 28 | function Google(clientId, clientSecret, userId, redirectURI, refreshToken) { 29 | _classCallCheck(this, Google); 30 | 31 | this.data = { 32 | clientId: clientId, 33 | clientSecret: clientSecret, 34 | userId: userId, 35 | redirectURI: redirectURI, 36 | refreshToken: refreshToken 37 | }; 38 | } 39 | 40 | /** 41 | * Generates an access token from instagram. Access tokens live forever. 42 | * BEST PRACTICE: log the access token and then store it in your env file. 43 | * Then you never have to call this method again. 44 | * 45 | * @param {string} code 46 | * @return {Promise} 47 | */ 48 | 49 | 50 | _createClass(Google, [{ 51 | key: 'initialize', 52 | value: function initialize(code) { 53 | var _this = this; 54 | 55 | // If someway, somehow this is already set (which it shouldn't be at this point) 56 | if (this.data.accessToken !== null) return Promise.resolve(this.data.accessToken); 57 | return new Promise(function (fulfill) { 58 | _request2.default.post('https://www.googleapis.com/oauth2/v4/token', { 59 | form: { 60 | client_id: _this.data.clientId, 61 | client_secret: _this.data.clientSecret, 62 | grant_type: 'authorization_code', 63 | redirect_uri: _this.data.redirectURI, 64 | code: code 65 | } 66 | }, function (error, response, body) { 67 | if (error || response.statusCode >= 400) { 68 | fulfill({ error: error }); 69 | } else { 70 | fulfill(body); 71 | } 72 | }); 73 | }); 74 | } 75 | 76 | /** 77 | * Refreshes access token which is required by Google 78 | * 79 | * @return {Promise} 80 | */ 81 | 82 | }, { 83 | key: 'refreshAccessToken', 84 | value: function refreshAccessToken() { 85 | var _this2 = this; 86 | 87 | return new Promise(function (fulfill, reject) { 88 | _request2.default.post('https://www.googleapis.com/oauth2/v4/token', { 89 | form: { 90 | refresh_token: _this2.data.refreshToken, 91 | client_id: _this2.data.clientId, 92 | client_secret: _this2.data.clientSecret, 93 | grant_type: 'refresh_token' 94 | } 95 | }, function (err, response, body) { 96 | if (err || response.statusCode >= 400) { 97 | reject(err || body); 98 | } else { 99 | fulfill(body); 100 | } 101 | }); 102 | }); 103 | } 104 | /** 105 | * Calls Google's API and gets posts 106 | * Access tokens last one hour 107 | * 108 | * @param {string} accessToken 109 | */ 110 | 111 | }, { 112 | key: 'fetch', 113 | value: function fetch() { 114 | var _this3 = this; 115 | 116 | return new Promise(function (fulfill, reject) { 117 | _this3.refreshAccessToken().then(function (res) { 118 | var token = JSON.parse(res).access_token; 119 | _request2.default.get('https://www.googleapis.com/plus/v1/people/' + _this3.data.userId + '/activities/public?access_token=' + token, function (err, response, body) { 120 | if (err || response.statusCode >= 400) { 121 | reject({ 122 | source: 'google', 123 | error: err || body 124 | }); 125 | } else { 126 | var output = JSON.parse(body).items; 127 | output.unshift({ 128 | id: output[0].actor.id, 129 | name: output[0].actor.displayName, 130 | profileImage: output[0].actor.image.url 131 | }); 132 | fulfill(_API2.default.normalize('google', output)); 133 | } 134 | }); 135 | }); 136 | }); 137 | } 138 | }]); 139 | 140 | return Google; 141 | }(); 142 | 143 | exports.default = Google; -------------------------------------------------------------------------------- /dist/classes/Instagram.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _request = require('request'); 10 | 11 | var _request2 = _interopRequireDefault(_request); 12 | 13 | var _API = require('./API'); 14 | 15 | var _API2 = _interopRequireDefault(_API); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | var Instagram = function () { 22 | /** 23 | * @param {string} clientId 24 | * @param {string} clientSecret 25 | * @param {string} userId 26 | * @param {string} redirectURI 27 | */ 28 | function Instagram(clientId, clientSecret, userId, redirectURI, accessToken) { 29 | _classCallCheck(this, Instagram); 30 | 31 | this.data = { 32 | clientId: clientId, 33 | clientSecret: clientSecret, 34 | redirectURI: redirectURI, 35 | userId: userId, 36 | accessToken: accessToken || null 37 | }; 38 | } 39 | 40 | /** 41 | * Generates an access token from instagram. Access tokens live forever. 42 | * BEST PRACTICE: log the access token and then store it in your env file. 43 | * Then you never have to call this method again. 44 | * 45 | * @param {string} code 46 | * @return {Promise} 47 | */ 48 | 49 | 50 | _createClass(Instagram, [{ 51 | key: 'initialize', 52 | value: function initialize(code) { 53 | var _this = this; 54 | 55 | // If someway, somehow this is already set (which it shouldn't be at this point) 56 | if (this.data.accessToken !== null) return Promise.resolve(this.data.accessToken); 57 | return new Promise(function (fulfill, reject) { 58 | _request2.default.post('https://api.instagram.com/oauth/access_token', { 59 | form: { 60 | client_id: _this.data.clientId, 61 | client_secret: _this.data.clientSecret, 62 | grant_type: 'authorization_code', 63 | redirect_uri: _this.data.redirectURI, 64 | code: code 65 | } 66 | }, function (err, response, body) { 67 | if (err || response.statusCode >= 400) { 68 | reject(err || body); 69 | } else { 70 | fulfill(body); 71 | } 72 | }); 73 | }); 74 | } 75 | 76 | /** 77 | * Calls instagram's api and gets user's latest posts 78 | * 79 | * @param {string} accessToken 80 | * @return {Promise} 81 | */ 82 | 83 | }, { 84 | key: 'fetch', 85 | value: function fetch(token) { 86 | var _this2 = this; 87 | 88 | // DEPRECATED. Pass to constructor. 89 | var accessToken = token || this.data.accessToken; 90 | return new Promise(function (fulfill) { 91 | (0, _request2.default)('https://api.instagram.com/v1/users/' + _this2.data.userId + '/media/recent/?access_token=' + accessToken, function (error, response, body) { 92 | if (error || response.statusCode >= 400) { 93 | fulfill({ 94 | error: error 95 | }); 96 | } else { 97 | var data = JSON.parse(body).data; 98 | data.unshift({ 99 | id: data[0].user.id, 100 | name: data[0].user.full_name, 101 | handle: data[0].user.username, 102 | profileImage: data[0].user.profile_picture 103 | }); 104 | fulfill(_API2.default.normalize('instagram', data)); 105 | } 106 | }); 107 | }); 108 | } 109 | }]); 110 | 111 | return Instagram; 112 | }(); 113 | 114 | exports.default = Instagram; -------------------------------------------------------------------------------- /dist/classes/Twitter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _twitter = require('twitter'); 10 | 11 | var _twitter2 = _interopRequireDefault(_twitter); 12 | 13 | var _API = require('./API'); 14 | 15 | var _API2 = _interopRequireDefault(_API); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 20 | 21 | var Twitter = function () { 22 | /** 23 | * @param {string} consumerKey 24 | * @param {string} consumerSecret 25 | * @param {string} accessTokenKey 26 | * @param {string} accessTokenSecret 27 | * @param {string} screenName 28 | * @param {string} excludeReplies 29 | */ 30 | function Twitter(consumerKey, consumerSecret, accessTokenKey, accessTokenSecret, screenName) { 31 | var options = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : { excludeReplies: false }; 32 | 33 | _classCallCheck(this, Twitter); 34 | 35 | this.screenName = screenName; 36 | this.options = options; 37 | this.client = new _twitter2.default({ 38 | consumer_key: consumerKey, 39 | consumer_secret: consumerSecret, 40 | access_token_key: accessTokenKey, 41 | access_token_secret: accessTokenSecret 42 | }); 43 | } 44 | 45 | /** 46 | * Calls twitters API and gets tweets 47 | * 48 | * @return {Promise} 49 | */ 50 | 51 | 52 | _createClass(Twitter, [{ 53 | key: 'fetch', 54 | value: function fetch() { 55 | var _this = this; 56 | 57 | return new Promise(function (fulfill) { 58 | _this.client.get('statuses/user_timeline', { 59 | screen_name: _this.screenName, 60 | exclude_replies: Object.keys(_this.options).indexOf('excludeReplies') > -1 ? _this.options.excludeReplies : false, 61 | count: _this.options.count || 20, 62 | include_rts: Object.keys(_this.options).indexOf('includeRts') > -1 ? _this.options.includeRts : true 63 | }, function (error, body, response) { 64 | if (error || response.statusCode >= 400) { 65 | fulfill({ error: error }); 66 | } else { 67 | body.unshift({ 68 | id: body[0].user.id_str, 69 | name: body[0].user.name, 70 | handle: body[0].user.screen_name, 71 | profileImage: body[0].user.profile_image_url_https 72 | }); 73 | fulfill(_API2.default.normalize('twitter', body)); 74 | } 75 | }); 76 | }); 77 | } 78 | }]); 79 | 80 | return Twitter; 81 | }(); 82 | 83 | exports.default = Twitter; -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _Facebook = require('./classes/Facebook'); 10 | 11 | var _Facebook2 = _interopRequireDefault(_Facebook); 12 | 13 | var _Twitter = require('./classes/Twitter'); 14 | 15 | var _Twitter2 = _interopRequireDefault(_Twitter); 16 | 17 | var _Instagram = require('./classes/Instagram'); 18 | 19 | var _Instagram2 = _interopRequireDefault(_Instagram); 20 | 21 | var _Google = require('./classes/Google'); 22 | 23 | var _Google2 = _interopRequireDefault(_Google); 24 | 25 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 26 | 27 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 28 | 29 | var SocialFeedAPI = function () { 30 | /** 31 | * @param {object} config 32 | */ 33 | function SocialFeedAPI(config) { 34 | _classCallCheck(this, SocialFeedAPI); 35 | 36 | if (config.facebook) { 37 | this.facebook = new _Facebook2.default(config.facebook.appId, config.facebook.appSecret, config.facebook.pageId, config.facebook.image); 38 | } 39 | if (config.twitter) { 40 | this.twitter = new _Twitter2.default(config.twitter.consumerKey, config.twitter.consumerSecret, config.twitter.accessTokenKey, config.twitter.accessTokenSecret, config.twitter.screenName, config.twitter.options); 41 | } 42 | if (config.instagram) { 43 | this.instagram = new _Instagram2.default(config.instagram.clientId, config.instagram.clientSecret, config.instagram.userId, config.instagram.redirectURI, config.instagram.accessToken); 44 | } 45 | if (config.google) { 46 | this.google = new _Google2.default(config.google.clientId, config.google.clientSecret, config.google.userId, config.google.redirectURI, config.google.refreshToken); 47 | } 48 | } 49 | 50 | /** 51 | * Generates an access token from instagram which is required 52 | * 53 | * @return {Promise} 54 | */ 55 | 56 | 57 | _createClass(SocialFeedAPI, [{ 58 | key: 'initializeInstagram', 59 | value: function initializeInstagram(code) { 60 | var _this = this; 61 | 62 | return new Promise(function (fulfill, reject) { 63 | _this.instagram.initialize(code).then(function (res) { 64 | console.log(res); 65 | fulfill(res); 66 | }, function (err) { 67 | console.log(err); 68 | reject(err); 69 | }); 70 | }); 71 | } 72 | 73 | /** 74 | * Generates an access token from Google which is required 75 | * 76 | * @return {Promise} 77 | */ 78 | 79 | }, { 80 | key: 'initializeGoogle', 81 | value: function initializeGoogle(code) { 82 | var _this2 = this; 83 | 84 | return new Promise(function (fulfill, reject) { 85 | _this2.google.initialize(code).then(function (res) { 86 | fulfill(res); 87 | }, function (err) { 88 | reject(err); 89 | }); 90 | }); 91 | } 92 | 93 | /** 94 | * Aggregates all social media feeds 95 | * 96 | * @param {object} accessTokens DEPRECATED: object of accessTokens 97 | * @return {Promise} 98 | */ 99 | 100 | }, { 101 | key: 'getFeeds', 102 | value: function getFeeds(accessTokens) { 103 | var _this3 = this; 104 | 105 | if (accessTokens) console.warn('NOTE: passing access tokens to getFeeds is now deprecated. Pass access token in constructor. See readme.md'); 106 | var instagram = null; 107 | if (this.instagram) { 108 | instagram = accessTokens ? this.instagram.fetch(accessTokens.instagram) : this.instagram.fetch(); 109 | } 110 | return new Promise(function (fulfill, reject) { 111 | Promise.all([_this3.facebook ? _this3.facebook.fetch() : Promise.resolve(null), _this3.twitter ? _this3.twitter.fetch() : Promise.resolve(null), _this3.instagram ? instagram : Promise.resolve(null), _this3.google ? _this3.google.fetch() : Promise.resolve(null)]).then(function (res) { 112 | fulfill({ 113 | facebook: res[0] || {}, 114 | twitter: res[1] || {}, 115 | instagram: res[2] || {}, 116 | google: res[3] || {} 117 | }); 118 | }, function (err) { 119 | reject(err); 120 | }); 121 | }); 122 | } 123 | }]); 124 | 125 | return SocialFeedAPI; 126 | }(); 127 | 128 | exports.default = SocialFeedAPI; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const babel = require('gulp-babel'); 3 | 4 | gulp.task('transpile-module', () => { 5 | return gulp.src(['./src/index.js']) 6 | .pipe(babel({ presets: ['es2015'] })) 7 | .pipe(gulp.dest('./dist')); 8 | }); 9 | 10 | gulp.task('transpile-classes', () => { 11 | return gulp.src(['./src/classes/*.js']) 12 | .pipe(babel({ presets: ['es2015'] })) 13 | .pipe(gulp.dest('./dist/classes')); 14 | }); 15 | 16 | gulp.task('transpile-env', () => { 17 | return gulp.src(['./src/env.js']) 18 | .pipe(babel({ presets: ['es2015'] })) 19 | .pipe(gulp.dest('./dist')); 20 | }); 21 | 22 | gulp.task('babel-module', () => { 23 | return gulp.watch(['./src/index.js'], ['transpile-module']); 24 | }); 25 | 26 | gulp.task('babel-classes', () => { 27 | return gulp.watch(['./src/classes/*.js'], ['transpile-classes']); 28 | }); 29 | 30 | gulp.task('default', [ 31 | 'transpile-module', 32 | 'transpile-classes', 33 | 'babel-module', 34 | 'babel-classes' 35 | ]); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "social-feed-api", 3 | "version": "1.1.3", 4 | "description": "Aggregates social media feeds and outputs them to use in an API", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/sourcetoad/node-social-feed-api.git" 12 | }, 13 | "keywords": [ 14 | "social", 15 | "media", 16 | "instagram", 17 | "facebook", 18 | "google", 19 | "twitter", 20 | "api" 21 | ], 22 | "author": "Max Kaplan", 23 | "license": "ISC", 24 | "bugs": { 25 | "url": "https://github.com/sourcetoad/node-social-feed-api/issues" 26 | }, 27 | "homepage": "https://github.com/sourcetoad/node-social-feed-api#readme", 28 | "devDependencies": { 29 | "babel-preset-es2015": "^6.24.0", 30 | "chai": "^3.5.0", 31 | "chai-http": "^3.0.0", 32 | "eslint": "^3.18.0", 33 | "eslint-config-airbnb": "^14.1.0", 34 | "eslint-config-sourcetoad": "^1.0.0", 35 | "eslint-plugin-import": "^2.2.0", 36 | "eslint-plugin-jsx-a11y": "^4.0.0", 37 | "eslint-plugin-react": "^6.10.3", 38 | "gulp": "^3.9.1", 39 | "gulp-babel": "^6.1.2", 40 | "mocha": "^3.4.2" 41 | }, 42 | "dependencies": { 43 | "request": "^2.81.0", 44 | "twitter": "^1.7.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/classes/API.js: -------------------------------------------------------------------------------- 1 | export default class API { 2 | /** 3 | * Normalizes data to make it consistent for all social networks 4 | * 5 | * @param {string} network Type of social network (facebook|twitter|instagram|google) 6 | * @param {array} data Data to normalize 7 | */ 8 | static normalize(network, data) { 9 | const items = []; 10 | // TODO: make these fields more customizable 11 | // TODO: loop through all attachments 12 | if (network === 'facebook') { 13 | for (let i = 1; i < data.length; i++) { 14 | items.push({ 15 | id: data[i].id, 16 | text: data[i].message, 17 | created_at: data[i].created_time, 18 | media: data[i].attachments && data[i].attachments.data[0] ? { 19 | images: data[i].attachments.data[0].type === 'photo' ? { 20 | standard: data[i].attachments.data[0].url, 21 | } : {}, 22 | videos: data[i].attachments.data[0].type === 'video_inline' ? { 23 | standard: data[i].attachments.data[0].url, 24 | } : {}, 25 | share: data[i].attachments.data[0].type === 'share' ? { 26 | standard: data[i].attachments.data[0].url, 27 | } : {}, 28 | } : {}, 29 | }); 30 | } 31 | } else if (network === 'twitter') { 32 | for (let i = 1; i < data.length; i++) { 33 | items.push({ 34 | id: data[i].id, 35 | text: data[i].text, 36 | created_at: new Date(data[i].created_at).toISOString(), 37 | media: data[i].entities.media ? { 38 | images: { 39 | standard: data[i].entities.media[0].media_url_https, 40 | }, 41 | } : {}, 42 | }); 43 | } 44 | } else if (network === 'instagram') { 45 | for (let i = 1; i < data.length; i++) { 46 | items.push({ 47 | id: data[i].id, 48 | text: data[i].caption ? data[i].caption.text : null, 49 | created_at: data[i].caption ? 50 | new Date(parseFloat(data[i].caption.created_time, 10) * 1000).toISOString() : null, 51 | media: { 52 | images: { 53 | low: data[i].images.low_resolution.url, 54 | standard: data[i].images.standard_resolution.url, 55 | thumbnail: data[i].images.thumbnail.url, 56 | }, 57 | videos: data[i].type === 'video' ? { 58 | low_bandwidth: data[i].videos.low_bandwidth.url, 59 | standard: data[i].videos.standard_resolution.url, 60 | low: data[i].videos.low_resolution.url, 61 | } : {}, 62 | }, 63 | }); 64 | } 65 | } else if (network === 'google') { 66 | for (let i = 1; i < data.length; i++) { 67 | items.push({ 68 | id: data[i].id, 69 | text: data[i].object.content, 70 | created_at: data[i].published, 71 | media: { 72 | images: data[i].object.attachments[0].objectType === 'photo' ? { 73 | standard: data[i].object.attachments[0].url, 74 | } : {}, 75 | videos: data[i].object.attachments[0].objectType === 'video' ? { 76 | standard: data[i].object.attachments[0].url, 77 | } : {}, 78 | }, 79 | }); 80 | } 81 | } 82 | 83 | return { 84 | _meta: { 85 | count: data.length - 1, 86 | }, 87 | account: data[0], 88 | items, 89 | }; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/classes/Facebook.js: -------------------------------------------------------------------------------- 1 | import request from 'request'; 2 | import API from './API'; 3 | 4 | export default class Facebook { 5 | /** 6 | * @param {string} appId 7 | * @param {string} appSecret 8 | * @param {string} pageId 9 | */ 10 | constructor(appId, appSecret, pageId, profileImage) { 11 | this.accessToken = null; 12 | this.data = { 13 | appId: appId || null, 14 | appSecret: appSecret || null, 15 | pageId: pageId || null, 16 | profileImage: profileImage || null, 17 | }; 18 | this.profileImageUrl = null; 19 | } 20 | 21 | /** 22 | * Generates an access token from facebook 23 | * 24 | * @return {Promise} 25 | */ 26 | getAccessToken() { 27 | return new Promise(fulfill => { 28 | request(`https://graph.facebook.com/oauth/access_token?grant_type=client_credentials&client_id=${this.data.appId}&client_secret=${this.data.appSecret}`, 29 | (error, response, body) => { 30 | if (error) fulfill({ error }); 31 | if (response.statusCode === 200) { 32 | this.accessToken = JSON.parse(body).access_token; 33 | fulfill(); 34 | } 35 | }, 36 | ); 37 | }); 38 | } 39 | 40 | /** 41 | * Fetches feed from facebook 42 | * 43 | * @return {Promise} 44 | */ 45 | getFeed() { 46 | return new Promise((fulfill, reject) => { 47 | // If picture is not set, get that first 48 | if (!this.profileImageUrl) { 49 | this.getProfileImage() 50 | .then(res => { 51 | this.profileImageUrl = res; 52 | request(`https://graph.facebook.com/${this.data.pageId}/posts?access_token=${this.accessToken}&fields=attachments,message,created_time,from`, (err, response, body) => { 53 | if (err || response.statusCode >= 400) { 54 | reject(err || body); 55 | } else { 56 | const output = JSON.parse(body).data; 57 | output.unshift({ 58 | id: output[0].from.id, 59 | name: output[0].from.name, 60 | profileImage: this.profileImageUrl, 61 | }); 62 | fulfill(output); 63 | } 64 | }); 65 | }); 66 | } else { 67 | request(`https://graph.facebook.com/${this.data.pageId}/posts?access_token=${this.accessToken}&fields=attachments,message,created_time,from`, (err, response, body) => { 68 | if (err || response.statusCode >= 400) { 69 | reject(err || body); 70 | } else { 71 | const output = JSON.parse(body).data; 72 | output.unshift({ 73 | name: output[0].from.name, 74 | profileImage: this.profileImageUrl, 75 | }); 76 | fulfill(output); 77 | } 78 | }); 79 | } 80 | }); 81 | } 82 | 83 | /** 84 | * Fetches profile picture 85 | */ 86 | getProfileImage() { 87 | return new Promise((fulfill, reject) => { 88 | request(`https://graph.facebook.com/${this.data.pageId}/picture?access_token=${this.accessToken}&redirect=false&height=${this.data.profileImage.height}&width=${this.data.profileImage.width}`, 89 | (err, response, body) => { 90 | if (err || response.statusCode >= 400) { 91 | reject(err || body); 92 | } else { 93 | fulfill(JSON.parse(body).data.url); 94 | } 95 | }); 96 | }); 97 | } 98 | 99 | /** 100 | * Method called from server 101 | * 102 | * @return Promise 103 | */ 104 | fetch() { 105 | return new Promise((fulfill, reject) => { 106 | // If no access token yet, get one 107 | if (this.accessToken === null) { 108 | this.getAccessToken() 109 | .then(() => { 110 | return this.getFeed(); 111 | }, err => { 112 | throw new Error(err); 113 | }) 114 | .then(res => { 115 | fulfill(API.normalize('facebook', res)); 116 | }, err => { 117 | reject({ 118 | source: 'facebook', 119 | error: err, 120 | }); 121 | }); 122 | } else { 123 | this.getFeed() 124 | .then(res => { 125 | fulfill(API.normalize('facebook', res)); 126 | }, err => { 127 | reject({ 128 | source: 'facebook', 129 | error: err, 130 | }); 131 | }); 132 | } 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/classes/Google.js: -------------------------------------------------------------------------------- 1 | import request from 'request'; 2 | import API from './API'; 3 | 4 | export default class Google { 5 | /** 6 | * @param {string} clientId 7 | * @param {string} clientSecret 8 | * @param {string} userId 9 | * @param {string} redirectURI 10 | */ 11 | constructor(clientId, clientSecret, userId, redirectURI, refreshToken) { 12 | this.data = { 13 | clientId, 14 | clientSecret, 15 | userId, 16 | redirectURI, 17 | refreshToken, 18 | }; 19 | } 20 | 21 | /** 22 | * Generates an access token from instagram. Access tokens live forever. 23 | * BEST PRACTICE: log the access token and then store it in your env file. 24 | * Then you never have to call this method again. 25 | * 26 | * @param {string} code 27 | * @return {Promise} 28 | */ 29 | initialize(code) { 30 | // If someway, somehow this is already set (which it shouldn't be at this point) 31 | if (this.data.accessToken !== null) return Promise.resolve(this.data.accessToken); 32 | return new Promise(fulfill => { 33 | request.post('https://www.googleapis.com/oauth2/v4/token', { 34 | form: { 35 | client_id: this.data.clientId, 36 | client_secret: this.data.clientSecret, 37 | grant_type: 'authorization_code', 38 | redirect_uri: this.data.redirectURI, 39 | code, 40 | }, 41 | }, (error, response, body) => { 42 | if (error || response.statusCode >= 400) { 43 | fulfill({ error }); 44 | } else { 45 | fulfill(body); 46 | } 47 | }); 48 | }); 49 | } 50 | 51 | /** 52 | * Refreshes access token which is required by Google 53 | * 54 | * @return {Promise} 55 | */ 56 | refreshAccessToken() { 57 | return new Promise((fulfill, reject) => { 58 | request.post('https://www.googleapis.com/oauth2/v4/token', { 59 | form: { 60 | refresh_token: this.data.refreshToken, 61 | client_id: this.data.clientId, 62 | client_secret: this.data.clientSecret, 63 | grant_type: 'refresh_token', 64 | }, 65 | }, (err, response, body) => { 66 | if (err || response.statusCode >= 400) { 67 | reject(err || body); 68 | } else { 69 | fulfill(body); 70 | } 71 | }); 72 | }); 73 | } 74 | /** 75 | * Calls Google's API and gets posts 76 | * Access tokens last one hour 77 | * 78 | * @param {string} accessToken 79 | */ 80 | fetch() { 81 | return new Promise((fulfill, reject) => { 82 | this.refreshAccessToken() 83 | .then(res => { 84 | const token = JSON.parse(res).access_token; 85 | request.get(`https://www.googleapis.com/plus/v1/people/${this.data.userId}/activities/public?access_token=${token}`, 86 | (err, response, body) => { 87 | if (err || response.statusCode >= 400) { 88 | reject({ 89 | source: 'google', 90 | error: err || body, 91 | }); 92 | } else { 93 | const output = JSON.parse(body).items; 94 | output.unshift({ 95 | id: output[0].actor.id, 96 | name: output[0].actor.displayName, 97 | profileImage: output[0].actor.image.url, 98 | }); 99 | fulfill(API.normalize('google', output)); 100 | } 101 | }); 102 | }); 103 | }); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/classes/Instagram.js: -------------------------------------------------------------------------------- 1 | import request from 'request'; 2 | import API from './API'; 3 | 4 | export default class Instagram { 5 | /** 6 | * @param {string} clientId 7 | * @param {string} clientSecret 8 | * @param {string} userId 9 | * @param {string} redirectURI 10 | */ 11 | constructor(clientId, clientSecret, userId, redirectURI, accessToken) { 12 | this.data = { 13 | clientId, 14 | clientSecret, 15 | redirectURI, 16 | userId, 17 | accessToken: accessToken || null, 18 | }; 19 | } 20 | 21 | /** 22 | * Generates an access token from instagram. Access tokens live forever. 23 | * BEST PRACTICE: log the access token and then store it in your env file. 24 | * Then you never have to call this method again. 25 | * 26 | * @param {string} code 27 | * @return {Promise} 28 | */ 29 | initialize(code) { 30 | // If someway, somehow this is already set (which it shouldn't be at this point) 31 | if (this.data.accessToken !== null) return Promise.resolve(this.data.accessToken); 32 | return new Promise((fulfill, reject) => { 33 | request.post('https://api.instagram.com/oauth/access_token', { 34 | form: { 35 | client_id: this.data.clientId, 36 | client_secret: this.data.clientSecret, 37 | grant_type: 'authorization_code', 38 | redirect_uri: this.data.redirectURI, 39 | code, 40 | }, 41 | }, (err, response, body) => { 42 | if (err || response.statusCode >= 400) { 43 | reject(err || body); 44 | } else { 45 | fulfill(body); 46 | } 47 | }); 48 | }); 49 | } 50 | 51 | /** 52 | * Calls instagram's api and gets user's latest posts 53 | * 54 | * @param {string} accessToken 55 | * @return {Promise} 56 | */ 57 | fetch(token) { 58 | // DEPRECATED. Pass to constructor. 59 | const accessToken = token || this.data.accessToken; 60 | return new Promise(fulfill => { 61 | request(`https://api.instagram.com/v1/users/${this.data.userId}/media/recent/?access_token=${accessToken}`, (error, response, body) => { 62 | if (error || response.statusCode >= 400) { 63 | fulfill({ 64 | error, 65 | }); 66 | } else { 67 | const data = JSON.parse(body).data; 68 | data.unshift({ 69 | id: data[0].user.id, 70 | name: data[0].user.full_name, 71 | handle: data[0].user.username, 72 | profileImage: data[0].user.profile_picture, 73 | }); 74 | fulfill(API.normalize('instagram', data)); 75 | } 76 | }); 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/classes/Twitter.js: -------------------------------------------------------------------------------- 1 | import TwitterAPI from 'twitter'; 2 | import API from './API'; 3 | 4 | export default class Twitter { 5 | /** 6 | * @param {string} consumerKey 7 | * @param {string} consumerSecret 8 | * @param {string} accessTokenKey 9 | * @param {string} accessTokenSecret 10 | * @param {string} screenName 11 | * @param {string} excludeReplies 12 | */ 13 | constructor(consumerKey, consumerSecret, accessTokenKey, accessTokenSecret, screenName, 14 | options = { excludeReplies: false }, 15 | ) { 16 | this.screenName = screenName; 17 | this.options = options; 18 | this.client = new TwitterAPI({ 19 | consumer_key: consumerKey, 20 | consumer_secret: consumerSecret, 21 | access_token_key: accessTokenKey, 22 | access_token_secret: accessTokenSecret, 23 | }); 24 | } 25 | 26 | /** 27 | * Calls twitters API and gets tweets 28 | * 29 | * @return {Promise} 30 | */ 31 | fetch() { 32 | return new Promise(fulfill => { 33 | this.client.get('statuses/user_timeline', { 34 | screen_name: this.screenName, 35 | exclude_replies: Object.keys(this.options).indexOf('excludeReplies') > -1 ? this.options.excludeReplies : false, 36 | count: this.options.count || 20, 37 | include_rts: Object.keys(this.options).indexOf('includeRts') > -1 ? this.options.includeRts : true, 38 | }, 39 | (error, body, response) => { 40 | if (error || response.statusCode >= 400) { 41 | fulfill({ error }); 42 | } else { 43 | body.unshift({ 44 | id: body[0].user.id_str, 45 | name: body[0].user.name, 46 | handle: body[0].user.screen_name, 47 | profileImage: body[0].user.profile_image_url_https, 48 | }); 49 | fulfill(API.normalize('twitter', body)); 50 | } 51 | }); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Facebook from './classes/Facebook'; 2 | import Twitter from './classes/Twitter'; 3 | import Instagram from './classes/Instagram'; 4 | import Google from './classes/Google'; 5 | 6 | export default class SocialFeedAPI { 7 | /** 8 | * @param {object} config 9 | */ 10 | constructor(config) { 11 | if (config.facebook) { 12 | this.facebook = new Facebook( 13 | config.facebook.appId, 14 | config.facebook.appSecret, 15 | config.facebook.pageId, 16 | config.facebook.image, 17 | ); 18 | } 19 | if (config.twitter) { 20 | this.twitter = new Twitter( 21 | config.twitter.consumerKey, 22 | config.twitter.consumerSecret, 23 | config.twitter.accessTokenKey, 24 | config.twitter.accessTokenSecret, 25 | config.twitter.screenName, 26 | config.twitter.options, 27 | ); 28 | } 29 | if (config.instagram) { 30 | this.instagram = new Instagram( 31 | config.instagram.clientId, 32 | config.instagram.clientSecret, 33 | config.instagram.userId, 34 | config.instagram.redirectURI, 35 | config.instagram.accessToken, 36 | ); 37 | } 38 | if (config.google) { 39 | this.google = new Google( 40 | config.google.clientId, 41 | config.google.clientSecret, 42 | config.google.userId, 43 | config.google.redirectURI, 44 | config.google.refreshToken, 45 | ); 46 | } 47 | } 48 | 49 | /** 50 | * Generates an access token from instagram which is required 51 | * 52 | * @return {Promise} 53 | */ 54 | initializeInstagram(code) { 55 | return new Promise((fulfill, reject) => { 56 | this.instagram.initialize(code) 57 | .then(res => { 58 | console.log(res); 59 | fulfill(res); 60 | }, err => { 61 | console.log(err); 62 | reject(err); 63 | }); 64 | }); 65 | } 66 | 67 | /** 68 | * Generates an access token from Google which is required 69 | * 70 | * @return {Promise} 71 | */ 72 | initializeGoogle(code) { 73 | return new Promise((fulfill, reject) => { 74 | this.google.initialize(code) 75 | .then(res => { 76 | fulfill(res); 77 | }, err => { 78 | reject(err); 79 | }); 80 | }); 81 | } 82 | 83 | /** 84 | * Aggregates all social media feeds 85 | * 86 | * @param {object} accessTokens DEPRECATED: object of accessTokens 87 | * @return {Promise} 88 | */ 89 | getFeeds(accessTokens) { 90 | if (accessTokens) console.warn('NOTE: passing access tokens to getFeeds is now deprecated. Pass access token in constructor. See readme.md'); 91 | let instagram = null; 92 | if (this.instagram) { 93 | instagram = accessTokens ? 94 | this.instagram.fetch(accessTokens.instagram) : this.instagram.fetch(); 95 | } 96 | return new Promise((fulfill, reject) => { 97 | Promise.all([ 98 | this.facebook ? this.facebook.fetch() : Promise.resolve(null), 99 | this.twitter ? this.twitter.fetch() : Promise.resolve(null), 100 | this.instagram ? instagram : Promise.resolve(null), 101 | this.google ? this.google.fetch() : Promise.resolve(null), 102 | ]) 103 | .then(res => { 104 | fulfill({ 105 | facebook: res[0] || {}, 106 | twitter: res[1] || {}, 107 | instagram: res[2] || {}, 108 | google: res[3] || {}, 109 | }); 110 | }, err => { 111 | reject(err); 112 | }); 113 | }); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const env = process.env.NODE_ENV === 'testing' ? process.env : require('./env.js'); 2 | const chai = require('chai'); 3 | const chaiHttp = require('chai-http'); 4 | const assert = chai.assert; 5 | const expect = chai.expect; 6 | const SocialFeed = require('../dist/').default; 7 | 8 | chai.use(chaiHttp); 9 | const social = new SocialFeed({ 10 | facebook: { 11 | appId: env.fbAppId, 12 | appSecret: env.fbAppSecret, 13 | pageId: env.fbPageId, 14 | image: { 15 | height: 100, 16 | width: 100, 17 | }, 18 | }, 19 | twitter: { 20 | consumerKey: env.twitterConsumerKey, 21 | consumerSecret: env.twitterConsumerSecret, 22 | accessTokenKey: env.twitterAccessTokenKey, 23 | accessTokenSecret: env.twitterAccessTokenSecret, 24 | screenName: env.twitterScreenName, 25 | }, 26 | instagram: { 27 | clientId: env.instagramClientId, 28 | clientSecret: env.instagramClientSecret, 29 | redirectURI: env.instagramRedirectURI, 30 | userId: env.instagramUserId, 31 | accessToken: env.instagramAccessToken, 32 | }, 33 | google: { 34 | clientId: env.googleClientId, 35 | clientSecret: env.googleClientSecret, 36 | userId: env.googleUserId, 37 | redirectURI: env.googleRedirectURI, 38 | refreshToken: env.googleRefreshToken, 39 | }, 40 | }); 41 | 42 | /** 43 | * Loops through feeds and tests each property 44 | * @param {array} feeds Networks to tests 45 | * @param {object} res Data returned from method 46 | */ 47 | function socialMediaParser(feeds, res) { 48 | for (let i = 0; i < feeds.length; i++) { 49 | assert.property(res, feeds[i], `${feeds[i]} property exists`); 50 | assert.property(res[feeds[i]], 'account', `${feeds[i]} account property exists`); 51 | assert.property(res[feeds[i]].account, 'name', `${feeds[i]} name property exists`); 52 | assert.property(res[feeds[i]].account, 'profileImage', `${feeds[i]} profileImage property exists`); 53 | assert.property(res[feeds[i]], 'items', `${feeds[i]} items property exists`); 54 | assert.property(res[feeds[i]], '_meta', `${feeds[i]} _meta property exists`); 55 | assert.isAbove(res[feeds[i]].items.length, 0, `Items returned from ${feeds[i]}`); 56 | if (feeds.indexOf('facebook') === -1) assert.notDeepProperty(res, 'facebook.items', 'facebook property should not exist'); 57 | if (feeds.indexOf('twitter') === -1) assert.notDeepProperty(res, 'twitter.items', 'twitter property should not exist'); 58 | if (feeds.indexOf('google') === -1) assert.notDeepProperty(res, 'google.items', 'google property should not exist'); 59 | if (feeds.indexOf('instagram') === -1) assert.notDeepProperty(res, 'instagram.items', 'instagram property should not exist'); 60 | } 61 | } 62 | 63 | describe('Test getFeeds() with all social networks', () => { 64 | it('should return successful with all sources', () => { 65 | return social.getFeeds() 66 | .then(res => { 67 | socialMediaParser(['facebook', 'twitter', 'instagram', 'google'], res); 68 | }, err => { 69 | console.error(`Error while fetching ${err.source}`, err); 70 | }); 71 | }); 72 | }); 73 | 74 | describe('Test getFeeds() with just facebook', () => { 75 | const network = new SocialFeed({ 76 | facebook: { 77 | appId: env.fbAppId, 78 | appSecret: env.fbAppSecret, 79 | pageId: env.fbPageId, 80 | image: { 81 | height: 100, 82 | width: 100, 83 | }, 84 | }, 85 | }); 86 | it('should return with just facebook', () => { 87 | return network.getFeeds() 88 | .then(res => { 89 | socialMediaParser(['facebook'], res); 90 | }); 91 | }); 92 | }); 93 | 94 | describe('Test getFeeds() with just twitter', () => { 95 | const network = new SocialFeed({ 96 | twitter: { 97 | consumerKey: env.twitterConsumerKey, 98 | consumerSecret: env.twitterConsumerSecret, 99 | accessTokenKey: env.twitterAccessTokenKey, 100 | accessTokenSecret: env.twitterAccessTokenSecret, 101 | screenName: env.twitterScreenName, 102 | options: { excludeReplies: true } 103 | }, 104 | }); 105 | it('should return with just twitter', () => { 106 | return network.getFeeds() 107 | .then(res => { 108 | socialMediaParser(['twitter'], res); 109 | }); 110 | }); 111 | }); 112 | 113 | describe('Test getFeeds() with just instagram', () => { 114 | const network = new SocialFeed({ 115 | instagram: { 116 | clientId: env.instagramClientId, 117 | clientSecret: env.instagramClientSecret, 118 | redirectURI: env.instagramRedirectURI, 119 | userId: env.instagramUserId, 120 | accessToken: env.instagramAccessToken, 121 | }, 122 | }); 123 | it('should return with just instagram', () => { 124 | return network.getFeeds() 125 | .then(res => { 126 | socialMediaParser(['instagram'], res); 127 | }); 128 | }); 129 | 130 | it('should return ok with deprecated instagram method', () => { 131 | const instaDeprecated = new SocialFeed({ 132 | instagram: { 133 | clientId: env.instagramClientId, 134 | clientSecret: env.instagramClientSecret, 135 | redirectURI: env.instagramRedirectURI, 136 | userId: env.instagramUserId, 137 | }, 138 | }); 139 | return instaDeprecated.getFeeds({ instagram: env.instagramAccessToken }) 140 | .then(res => { 141 | socialMediaParser(['instagram'], res); 142 | }); 143 | }); 144 | }); 145 | 146 | describe('Test getFeeds() with just google', () => { 147 | const network = new SocialFeed({ 148 | google: { 149 | clientId: env.googleClientId, 150 | clientSecret: env.googleClientSecret, 151 | userId: env.googleUserId, 152 | redirectURI: env.googleRedirectURI, 153 | refreshToken: env.googleRefreshToken, 154 | }, 155 | }); 156 | it('should return with just google', () => { 157 | return network.getFeeds() 158 | .then(res => { 159 | socialMediaParser(['google'], res); 160 | }); 161 | }); 162 | }); 163 | --------------------------------------------------------------------------------