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 | |
| parent | 1b8b6e76bf175c4d0ead8d3fc6f01558066cbec9 (diff) | |
added LLTwitterConnect for ACME-1133
| -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)  | 
