diff options
author | Cho <cho@lindenlab.com> | 2013-11-01 23:12:45 +0100 |
---|---|---|
committer | Cho <cho@lindenlab.com> | 2013-11-01 23:12:45 +0100 |
commit | 963c97f64a4d1490fe8380805c4de38598adddad (patch) | |
tree | 7e9a3072814ae4e60fe7d35b16d5cb7e87ed807f /indra | |
parent | 1b8b6e76bf175c4d0ead8d3fc6f01558066cbec9 (diff) |
added LLTwitterConnect for ACME-1133
Diffstat (limited to 'indra')
-rwxr-xr-x | indra/newview/CMakeLists.txt | 2 | ||||
-rw-r--r-- | indra/newview/lltwitterconnect.cpp | 503 | ||||
-rw-r--r-- | indra/newview/lltwitterconnect.h | 99 | ||||
-rwxr-xr-x | indra/newview/skins/default/xui/en/notifications.xml | 7 | ||||
-rwxr-xr-x | indra/newview/skins/default/xui/en/strings.xml | 3 |
5 files changed, 614 insertions, 0 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 6f5deb639c..ac2d3f8ff4 100755 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -568,6 +568,7 @@ set(viewer_SOURCE_FILES lltransientdockablefloater.cpp lltransientfloatermgr.cpp lltranslate.cpp + lltwitterconnect.cpp lluilistener.cpp lluploaddialog.cpp lluploadfloaterobservers.cpp @@ -1147,6 +1148,7 @@ set(viewer_HEADER_FILES lltransientdockablefloater.h lltransientfloatermgr.h lltranslate.h + lltwitterconnect.h lluiconstants.h lluilistener.h lluploaddialog.h diff --git a/indra/newview/lltwitterconnect.cpp b/indra/newview/lltwitterconnect.cpp new file mode 100644 index 0000000000..6efcce8127 --- /dev/null +++ b/indra/newview/lltwitterconnect.cpp @@ -0,0 +1,503 @@ +/** + * @file lltwitterconnect.h + * @author Merov, Cho + * @brief Connection to Twitter Service + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltwitterconnect.h" + +#include "llagent.h" +#include "llcallingcard.h" // for LLAvatarTracker +#include "llcommandhandler.h" +#include "llhttpclient.h" +#include "llnotificationsutil.h" +#include "llurlaction.h" +#include "llimagepng.h" +#include "llimagejpeg.h" +#include "lltrans.h" +#include "llevents.h" +#include "llviewerregion.h" + +#include "llfloaterwebcontent.h" +#include "llfloaterreg.h" + +boost::scoped_ptr<LLEventPump> LLTwitterConnect::sStateWatcher(new LLEventStream("TwitterConnectState")); +boost::scoped_ptr<LLEventPump> LLTwitterConnect::sInfoWatcher(new LLEventStream("TwitterConnectInfo")); +boost::scoped_ptr<LLEventPump> LLTwitterConnect::sContentWatcher(new LLEventStream("TwitterConnectContent")); + +// Local functions +void log_twitter_connect_error(const std::string& request, U32 status, const std::string& reason, const std::string& code, const std::string& description) +{ + // Note: 302 (redirect) is *not* an error that warrants logging + if (status != 302) + { + LL_WARNS("TwitterConnect") << request << " request failed with a " << status << " " << reason << ". Reason: " << code << " (" << description << ")" << LL_ENDL; + } +} + +void toast_user_for_twitter_success() +{ + LLSD args; + args["MESSAGE"] = LLTrans::getString("twitter_post_success"); + LLNotificationsUtil::add("TwitterConnect", args); +} + +/////////////////////////////////////////////////////////////////////////////// +// +class LLTwitterConnectHandler : public LLCommandHandler +{ +public: + LLTwitterConnectHandler() : LLCommandHandler("fbc", UNTRUSTED_THROTTLE) { } + + bool handle(const LLSD& tokens, const LLSD& query_map, LLMediaCtrl* web) + { + if (tokens.size() >= 2) + { + if (tokens[0].asString() == "connect" && tokens[1].asString() == "twitter") + { + // this command probably came from the fbc_web browser, so close it + LLFloater* fbc_web = LLFloaterReg::getInstance("fbc_web"); + if (fbc_web) + { + fbc_web->closeFloater(); + } + + // connect to twitter + if (query_map.has("oauth_token")) + { + LLTwitterConnect::instance().connectToTwitter(query_map["oauth_token"], query_map.get("oauth_verifier")); + } + return true; + } + } + return false; + } +}; +LLTwitterConnectHandler gTwitterConnectHandler; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLTwitterConnectResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLTwitterConnectResponder); +public: + + LLTwitterConnectResponder() + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTION_IN_PROGRESS); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("TwitterConnect") << "Connect successful. content: " << content << LL_ENDL; + + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTED); + } + else if (status != 302) + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTION_FAILED); + log_twitter_connect_error("Connect", status, reason, content.get("error_code"), content.get("error_description")); + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLTwitterConnect::instance().openTwitterWeb(content["location"]); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLTwitterShareResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLTwitterShareResponder); +public: + + LLTwitterShareResponder() + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_POSTING); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + toast_user_for_twitter_success(); + LL_DEBUGS("TwitterConnect") << "Post successful. content: " << content << LL_ENDL; + + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_POSTED); + } + else if (status == 404) + { + LLTwitterConnect::instance().connectToTwitter(); + } + else + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_POST_FAILED); + log_twitter_connect_error("Share", status, reason, content.get("error_code"), content.get("error_description")); + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLTwitterConnect::instance().openTwitterWeb(content["location"]); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLTwitterDisconnectResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLTwitterDisconnectResponder); +public: + + LLTwitterDisconnectResponder() + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_DISCONNECTING); + } + + void setUserDisconnected() + { + // Clear data + LLTwitterConnect::instance().clearInfo(); + + //Notify state change + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_NOT_CONNECTED); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("TwitterConnect") << "Disconnect successful. content: " << content << LL_ENDL; + setUserDisconnected(); + + } + //User not found so already disconnected + else if(status == 404) + { + LL_DEBUGS("TwitterConnect") << "Already disconnected. content: " << content << LL_ENDL; + setUserDisconnected(); + } + else + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_DISCONNECT_FAILED); + log_twitter_connect_error("Disconnect", status, reason, content.get("error_code"), content.get("error_description")); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLTwitterConnectedResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLTwitterConnectedResponder); +public: + + LLTwitterConnectedResponder(bool auto_connect) : mAutoConnect(auto_connect) + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTION_IN_PROGRESS); + } + + virtual void completed(U32 status, const std::string& reason, const LLSD& content) + { + if (isGoodStatus(status)) + { + LL_DEBUGS("TwitterConnect") << "Connect successful. content: " << content << LL_ENDL; + + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTED); + } + else + { + // show the twitter login page if not connected yet + if (status == 404) + { + if (mAutoConnect) + { + LLTwitterConnect::instance().connectToTwitter(); + } + else + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_NOT_CONNECTED); + } + } + else + { + LLTwitterConnect::instance().setConnectionState(LLTwitterConnect::TWITTER_CONNECTION_FAILED); + log_twitter_connect_error("Connected", status, reason, content.get("error_code"), content.get("error_description")); + } + } + } + +private: + bool mAutoConnect; +}; + +/////////////////////////////////////////////////////////////////////////////// +// +class LLTwitterInfoResponder : public LLHTTPClient::Responder +{ + LOG_CLASS(LLTwitterInfoResponder); +public: + + virtual void completed(U32 status, const std::string& reason, const LLSD& info) + { + if (isGoodStatus(status)) + { + llinfos << "Twitter: Info received" << llendl; + LL_DEBUGS("TwitterConnect") << "Getting Twitter info successful. info: " << info << LL_ENDL; + LLTwitterConnect::instance().storeInfo(info); + } + else + { + log_twitter_connect_error("Info", status, reason, info.get("error_code"), info.get("error_description")); + } + } + + void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + if (status == 302) + { + LLTwitterConnect::instance().openTwitterWeb(content["location"]); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// +LLTwitterConnect::LLTwitterConnect() +: mConnectionState(TWITTER_NOT_CONNECTED), + mConnected(false), + mInfo(), + mRefreshInfo(false), + mReadFromMaster(false) +{ +} + +void LLTwitterConnect::openTwitterWeb(std::string url) +{ + // Open the URL in an internal browser window without navigation UI + LLFloaterWebContent::Params p; + p.url(url).show_chrome(true); + p.url(url).allow_address_entry(false); + p.url(url).allow_back_forward_navigation(false); + p.url(url).trusted_content(true); + LLFloater *floater = LLFloaterReg::showInstance("fbc_web", p); + //the internal web browser has a bug that prevents it from gaining focus unless a mouse event occurs first (it seems). + //So when showing the internal web browser, set focus to it's containing floater "fbc_web". When a mouse event + //occurs on the "webbrowser" panel part of the floater, a mouse cursor will properly show and the "webbrowser" will gain focus. + //fbc_web floater contains the "webbrowser" panel. JIRA: ACME-744 + gFocusMgr.setKeyboardFocus( floater ); + + //LLUrlAction::openURLExternal(url); +} + +std::string LLTwitterConnect::getTwitterConnectURL(const std::string& route, bool include_read_from_master) +{ + std::string url(""); + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) + { + url = regionp->getCapability("TwitterConnect"); + url += route; + + if (include_read_from_master && mReadFromMaster) + { + url += "?read_from_master=true"; + } + } + return url; +} + +void LLTwitterConnect::connectToTwitter(const std::string& request_token, const std::string& oauth_verifier) +{ + LLSD body; + if (!request_token.empty()) + body["request_token"] = request_token; + if (!oauth_verifier.empty()) + body["oauth_verifier"] = oauth_verifier; + + LLHTTPClient::put(getTwitterConnectURL("/connection"), body, new LLTwitterConnectResponder()); +} + +void LLTwitterConnect::disconnectFromTwitter() +{ + LLHTTPClient::del(getTwitterConnectURL("/connection"), new LLTwitterDisconnectResponder()); +} + +void LLTwitterConnect::checkConnectionToTwitter(bool auto_connect) +{ + const bool follow_redirects = false; + const F32 timeout = HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getTwitterConnectURL("/connection", true), new LLTwitterConnectedResponder(auto_connect), + LLSD(), timeout, follow_redirects); +} + +void LLTwitterConnect::loadTwitterInfo() +{ + if(mRefreshInfo) + { + const bool follow_redirects = false; + const F32 timeout = HTTP_REQUEST_EXPIRY_SECS; + LLHTTPClient::get(getTwitterConnectURL("/info", true), new LLTwitterInfoResponder(), + LLSD(), timeout, follow_redirects); + } +} + +void LLTwitterConnect::uploadPhoto(const std::string& image_url, const std::string& status) +{ + LLSD body; + body["image"] = image_url; + body["status"] = status; + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::post(getTwitterConnectURL("/share/photo", true), body, new LLTwitterShareResponder()); +} + +void LLTwitterConnect::uploadPhoto(LLPointer<LLImageFormatted> image, const std::string& status) +{ + std::string imageFormat; + if (dynamic_cast<LLImagePNG*>(image.get())) + { + imageFormat = "png"; + } + else if (dynamic_cast<LLImageJPEG*>(image.get())) + { + imageFormat = "jpg"; + } + else + { + llwarns << "Image to upload is not a PNG or JPEG" << llendl; + return; + } + + // All this code is mostly copied from LLWebProfile::post() + const std::string boundary = "----------------------------0123abcdefab"; + + LLSD headers; + headers["Content-Type"] = "multipart/form-data; boundary=" + boundary; + + std::ostringstream body; + + // *NOTE: The order seems to matter. + body << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"status\"\r\n\r\n" + << status << "\r\n"; + + body << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"image\"; filename=\"snapshot." << imageFormat << "\"\r\n" + << "Content-Type: image/" << imageFormat << "\r\n\r\n"; + + // Insert the image data. + // *FIX: Treating this as a string will probably screw it up ... + U8* image_data = image->getData(); + for (S32 i = 0; i < image->getDataSize(); ++i) + { + body << image_data[i]; + } + + body << "\r\n--" << boundary << "--\r\n"; + + // postRaw() takes ownership of the buffer and releases it later. + size_t size = body.str().size(); + U8 *data = new U8[size]; + memcpy(data, body.str().data(), size); + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::postRaw(getTwitterConnectURL("/share/photo", true), data, size, new LLTwitterShareResponder(), headers); +} + +void LLTwitterConnect::updateStatus(const std::string& status) +{ + LLSD body; + body["status"] = status; + + // Note: we can use that route for different publish action. We should be able to use the same responder. + LLHTTPClient::post(getTwitterConnectURL("/share/status", true), body, new LLTwitterShareResponder()); +} + +void LLTwitterConnect::storeInfo(const LLSD& info) +{ + mInfo = info; + mRefreshInfo = false; + + sInfoWatcher->post(info); +} + +const LLSD& LLTwitterConnect::getInfo() const +{ + return mInfo; +} + +void LLTwitterConnect::clearInfo() +{ + mInfo = LLSD(); +} + +void LLTwitterConnect::setDataDirty() +{ + mRefreshInfo = true; +} + +void LLTwitterConnect::setConnectionState(LLTwitterConnect::EConnectionState connection_state) +{ + if(connection_state == TWITTER_CONNECTED) + { + mReadFromMaster = true; + setConnected(true); + setDataDirty(); + } + else if(connection_state == TWITTER_NOT_CONNECTED) + { + setConnected(false); + } + else if(connection_state == TWITTER_POSTED) + { + mReadFromMaster = false; + } + + if (mConnectionState != connection_state) + { + LLSD state_info; + state_info["enum"] = connection_state; + sStateWatcher->post(state_info); + } + + mConnectionState = connection_state; +} + +void LLTwitterConnect::setConnected(bool connected) +{ + mConnected = connected; +} diff --git a/indra/newview/lltwitterconnect.h b/indra/newview/lltwitterconnect.h new file mode 100644 index 0000000000..c1df13f18c --- /dev/null +++ b/indra/newview/lltwitterconnect.h @@ -0,0 +1,99 @@ +/** + * @file lltwitterconnect.h + * @author Merov, Cho + * @brief Connection to Twitter Service + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTWITTERCONNECT_H +#define LL_LLTWITTERCONNECT_H + +#include "llsingleton.h" +#include "llimage.h" + +class LLEventPump; + +/** + * @class LLTwitterConnect + * + * Manages authentication to, and interaction with, a web service allowing the + * the viewer to post status updates and upload photos to Twitter. + */ +class LLTwitterConnect : public LLSingleton<LLTwitterConnect> +{ + LOG_CLASS(LLTwitterConnect); +public: + enum EConnectionState + { + TWITTER_NOT_CONNECTED = 0, + TWITTER_CONNECTION_IN_PROGRESS = 1, + TWITTER_CONNECTED = 2, + TWITTER_CONNECTION_FAILED = 3, + TWITTER_POSTING = 4, + TWITTER_POSTED = 5, + TWITTER_POST_FAILED = 6, + TWITTER_DISCONNECTING = 7, + TWITTER_DISCONNECT_FAILED = 8 + }; + + void connectToTwitter(const std::string& request_token = "", const std::string& oauth_verifier = ""); // Initiate the complete Twitter connection. Please use checkConnectionToTwitter() in normal use. + void disconnectFromTwitter(); // Disconnect from the Twitter service. + void checkConnectionToTwitter(bool auto_connect = false); // Check if an access token is available on the Twitter service. If not, call connectToTwitter(). + + void loadTwitterInfo(); + void uploadPhoto(const std::string& image_url, const std::string& status); + void uploadPhoto(LLPointer<LLImageFormatted> image, const std::string& status); + void updateStatus(const std::string& status); + + void storeInfo(const LLSD& info); + const LLSD& getInfo() const; + void clearInfo(); + void setDataDirty(); + + void setConnectionState(EConnectionState connection_state); + void setConnected(bool connected); + bool isConnected() { return mConnected; } + bool isTransactionOngoing() { return ((mConnectionState == TWITTER_CONNECTION_IN_PROGRESS) || (mConnectionState == TWITTER_POSTING) || (mConnectionState == TWITTER_DISCONNECTING)); } + EConnectionState getConnectionState() { return mConnectionState; } + + void openTwitterWeb(std::string url); + +private: + friend class LLSingleton<LLTwitterConnect>; + + LLTwitterConnect(); + ~LLTwitterConnect() {}; + std::string getTwitterConnectURL(const std::string& route = "", bool include_read_from_master = false); + + EConnectionState mConnectionState; + BOOL mConnected; + LLSD mInfo; + bool mRefreshInfo; + bool mReadFromMaster; + + static boost::scoped_ptr<LLEventPump> sStateWatcher; + static boost::scoped_ptr<LLEventPump> sInfoWatcher; + static boost::scoped_ptr<LLEventPump> sContentWatcher; +}; + +#endif // LL_LLTWITTERCONNECT_H diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index b562c5cb5e..a3c0682aea 100755 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -6054,6 +6054,13 @@ Please select at least one type of content to search (General, Moderate, or Adul <notification icon="notify.tga" + name="TwitterConnect" + type="notifytip"> + [MESSAGE] + </notification> + + <notification + icon="notify.tga" name="PaymentReceived" log_to_im="true" persist="true" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 0d2083632d..df04ddee86 100755 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -3445,6 +3445,9 @@ If you continue to receive this message, contact the [SUPPORT_SITE]. <string name="flickr_post_success"> You posted to Flickr. </string> + <string name="twitter_post_success"> + You posted to Twitter. + </string> <string name="no_session_message"> (IM Session Doesn't Exist) |