/** * Copyright (c) 2010-2016 by the respective copyright holders. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.openhab.action.twitter.internal; import static org.apache.commons.lang.StringUtils.*; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Dictionary; import java.util.Properties; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.openhab.core.scriptengine.action.ActionService; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.cm.ManagedService; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import twitter4j.TwitterFactory; import twitter4j.auth.AccessToken; import twitter4j.auth.RequestToken; /** * This class registers an OSGi service for the Twitter action. * * @author Ben Jones * @author Thomas.Eichstaedt-Engelen * @author Kai Kreuzer * @since 1.3.0 */ public class TwitterActionService implements ActionService, ManagedService { private static final Logger logger = LoggerFactory.getLogger(TwitterActionService.class); /** * Indicates whether this action is properly configured which means all * necessary configurations are set. This flag can be checked by the * action methods before executing code. */ /* default */ static boolean isProperlyConfigured = false; /** the configured ConsumerKey (optional, defaults to the official Twitter-App key 'IPqVDkyMvhblm7pobBMYw') */ static String consumerKey = "IPqVDkyMvhblm7pobBMYw"; /** * the configured ConsumerSecret (optional, defaults to official Twitter-App secret * '2HatstDfLbz236WCXyf8lKCk985HdaK5zbXFrcJ2BM') */ static String consumerSecret = "2HatstDfLbz236WCXyf8lKCk985HdaK5zbXFrcJ2BM"; private static final String TOKEN_FILE = "twitter.token"; private static File tokenFile; public TwitterActionService() { } public void activate(ComponentContext componentContext) { } public void deactivate(ComponentContext componentContext) { // deallocate resources here that are no longer needed and // should be reset when activating this binding again } @Override public String getActionClassName() { return Twitter.class.getCanonicalName(); } @Override public Class<?> getActionClass() { return Twitter.class; } /** * Creates and returns a Twitter4J Twitter client. * * @return a new instance of a Twitter4J Twitter client. */ private static twitter4j.Twitter createClient() { twitter4j.Twitter client = TwitterFactory.getSingleton(); client.setOAuthConsumer(consumerKey, consumerSecret); return client; } private static void start() { if (!Twitter.isEnabled) { return; } Twitter.client = createClient(); AccessToken accessToken = getAccessToken(); if (accessToken != null) { Twitter.client.setOAuthAccessToken(accessToken); logger.info("TwitterAction has been successfully authenticated > awaiting your Tweets!"); } else { logger.info("Twitter authentication failed. Please use OSGi " + "console to restart the org.openhab.io.net-Bundle and re-initiate the authorization process!"); } } private static AccessToken getAccessToken() { try { String accessToken = loadToken(getTokenFile(), "accesstoken"); String accessTokenSecret = loadToken(getTokenFile(), "accesstokensecret"); if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(accessTokenSecret)) { File pinFile = new File("twitter.pin"); RequestToken requestToken = Twitter.client.getOAuthRequestToken(); // no access token/secret specified so display the authorisation URL in the log logger.info( "################################################################################################"); logger.info("# Twitter-Integration: U S E R I N T E R A C T I O N R E Q U I R E D !!"); logger.info("# 1. Open URL '{}'", requestToken.getAuthorizationURL()); logger.info("# 2. Grant openHAB access to your Twitter account"); logger.info("# 3. Create an empty file 'twitter.pin' in your openHAB home directory at " + pinFile.getAbsolutePath()); logger.info("# 4. Add the line 'pin=<authpin>' to the twitter.pin file"); logger.info("# 5. openHAB will automatically detect the file and complete the authentication process"); logger.info("# NOTE: You will only have 5 mins before openHAB gives up waiting for the pin!!!"); logger.info( "################################################################################################"); String authPin = null; int interval = 5000; int waitedFor = 0; while (StringUtils.isEmpty(authPin)) { try { Thread.sleep(interval); waitedFor += interval; // attempt to read the authentication pin from them temp file } catch (InterruptedException e) { // ignore } authPin = loadToken(pinFile, "pin"); // if we already waited for more than five minutes then stop if (waitedFor > 300000) { logger.info("Took too long to enter your Twitter authorisation pin! Please use OSGi " + "console to restart the org.openhab.io.net-Bundle and re-initiate the authorization process!"); break; } } // if no pin was detected after 5 mins then we can't continue if (StringUtils.isEmpty(authPin)) { logger.warn("Timed out waiting for the Twitter authorisation pin."); return null; } // attempt to get an access token using the user-entered pin AccessToken token = Twitter.client.getOAuthAccessToken(requestToken, authPin); accessToken = token.getToken(); accessTokenSecret = token.getTokenSecret(); // save the access token details saveToken(getTokenFile(), "accesstoken", accessToken); saveToken(getTokenFile(), "accesstokensecret", accessTokenSecret); } // generate an access token from the token details return new AccessToken(accessToken, accessTokenSecret); } catch (Exception e) { logger.error("Failed to authenticate openHAB against Twitter", e); return null; } } // Helpers for storing/retrieving tokens from a flat file private static File getTokenFile() { if (tokenFile == null) { File tokenPath = null; String userdata = System.getProperty("smarthome.userdata"); if (StringUtils.isEmpty(userdata)) { tokenPath = new File("etc"); } else { tokenPath = new File(userdata); } tokenFile = new File(tokenPath, TOKEN_FILE); } return tokenFile; } private static String loadToken(File file, String key) throws IOException { Properties properties = loadProperties(file); String token = (String) properties.get(key); if (StringUtils.isEmpty(token)) { return null; } return token; } private static Properties loadProperties(File file) throws IOException { Properties properties = new Properties(); try { FileInputStream inputStream = new FileInputStream(file); try { properties.load(inputStream); } finally { IOUtils.closeQuietly(inputStream); } } catch (FileNotFoundException e) { // ignore a missing file exception } return properties; } private static void saveToken(File file, String key, String token) throws IOException { Properties properties = loadProperties(file); if (token == null) { token = ""; } properties.setProperty(key, token); saveProperties(file, properties); } private static void saveProperties(File file, Properties properties) throws IOException { FileOutputStream outputStream = new FileOutputStream(file); try { properties.store(outputStream, "Twitter OAuth Authentication Tokens"); } finally { IOUtils.closeQuietly(outputStream); } } /** * @{inheritDoc} */ @Override public void updated(Dictionary<String, ?> config) throws ConfigurationException { if (config != null) { String appKeyString = (String) config.get("key"); if (isNotBlank(appKeyString)) { consumerKey = appKeyString; } String appSecretString = (String) config.get("secret"); if (isNotBlank(appSecretString)) { consumerSecret = appSecretString; } if (isBlank(consumerKey) || isBlank(consumerSecret)) { throw new ConfigurationException("twitter", "The parameters 'key' or 'secret' are missing! Please refer to your 'openhab.cfg'"); } String enabledString = (String) config.get("enabled"); if (StringUtils.isNotBlank(enabledString)) { Twitter.isEnabled = Boolean.parseBoolean(enabledString); } isProperlyConfigured = true; start(); } } }