/* * Juick * Copyright (C) 2013, Ugnich Anton * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package com.juick; import com.juick.server.CrosspostQueries; import com.juick.xmpp.extensions.JuickMessage; import com.juick.xmpp.utils.Base64; import org.apache.commons.lang3.tuple.Pair; import org.springframework.jdbc.core.JdbcTemplate; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.HttpsURLConnection; import java.io.*; import java.net.URL; import java.net.URLEncoder; import java.security.Key; import java.util.Properties; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author Ugnich Anton */ public class CrosspostComponent implements JuickComponent { private static Logger logger = Logger.getLogger(CrosspostComponent.class.getName()); public final static String TWITTERURL = "https://api.twitter.com/1.1/statuses/update.json"; public final static String FBURL = "https://graph.facebook.com/me/feed"; public final static String VKURL = "https://api.vk.com/method/wall.post"; JdbcTemplate sql; String twitter_consumer_key; String twitter_consumer_secret; public CrosspostComponent(JdbcTemplate sql, Properties conf) { logger.info("component initialized"); twitter_consumer_key = conf.getProperty("twitter_consumer_key", ""); twitter_consumer_secret = conf.getProperty("twitter_consumer_secret", ""); this.sql = sql; } @Override public void messageReceived(com.juick.xmpp.Message msg) { JuickMessage jmsg = (JuickMessage) msg.getChild(JuickMessage.XMLNS); if (msg.to != null && msg.to.Username != null && jmsg != null && jmsg.getRID() == 0) { if (msg.to.Username.equals("twitter")) { twitterPost(jmsg); } else if (msg.to.Username.equals("fb")) { facebookPost(jmsg); } else if (msg.to.Username.equals("vk")) { vkontaktePost(jmsg); } } } public boolean facebookPost(com.juick.Message jmsg) { String token = CrosspostQueries.getFacebookToken(sql, jmsg.getUser().getUID()); if (token == null) { return false; } logger.info("FB: #" + jmsg.getMID()); String status = getMessageHashTags(jmsg) + "\n" + jmsg.getText(); boolean ret = false; try { String body = "access_token=" + URLEncoder.encode(token, "UTF-8") + "&message=" + URLEncoder.encode(status, "UTF-8") + "&link=http%3A%2F%2Fjuick.com%2F" + jmsg.getMID(); HttpsURLConnection conn = (HttpsURLConnection) new URL(FBURL).openConnection(); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("User-Agent", "Juick"); conn.setRequestProperty("Content-Length", Integer.toString(body.length())); conn.setUseCaches(false); conn.setDoInput(true); conn.setDoOutput(true); conn.setRequestMethod("POST"); conn.connect(); OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream()); wr.write(body); wr.close(); ret = streamToString(conn.getInputStream()) != null; conn.disconnect(); } catch (Exception e) { logger.log(Level.SEVERE, "fbPost: " + e.getMessage(), e); } return ret; } public boolean vkontaktePost(com.juick.Message jmsg) { Pair tokens = CrosspostQueries.getVKTokens(sql, jmsg.getUser().getUID()); if (tokens == null) { return false; } logger.info("VK: #" + jmsg.getMID()); String status = getMessageHashTags(jmsg) + "\n" + jmsg.getText() + "\nhttp://juick.com/" + jmsg.getMID(); boolean ret = false; try { String body = "owner_id=" + tokens.getLeft() + "&access_token=" + URLEncoder.encode(tokens.getRight(), "UTF-8") + "&from_group=1&message=" + URLEncoder.encode(status, "UTF-8"); HttpsURLConnection conn = (HttpsURLConnection) new URL(VKURL).openConnection(); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("User-Agent", "Juick"); conn.setRequestProperty("Content-Length", Integer.toString(body.length())); conn.setUseCaches(false); conn.setDoInput(true); conn.setDoOutput(true); conn.setRequestMethod("POST"); conn.connect(); OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream()); wr.write(body); wr.close(); ret = streamToString(conn.getInputStream()) != null; conn.disconnect(); } catch (Exception e) { logger.log(Level.SEVERE, "vkPost: " + e.getMessage(), e); } return ret; } public boolean twitterPost(com.juick.Message jmsg) { Pair tokens = CrosspostQueries.getTwitterTokens(sql, jmsg.getUser().getUID()); if (tokens == null) { return false; } String token = percentEncode(tokens.getLeft()); String token_secret = percentEncode(tokens.getRight()); logger.info("TWITTER: #" + jmsg.getMID()); String status = getMessageHashTags(jmsg) + jmsg.getText(); if (status.length() > 115) { status = status.substring(0, 114) + "…"; } status += " http://juick.com/" + jmsg.getMID(); status = percentEncode(status); boolean ret = false; try { String nonce = UUID.randomUUID().toString(); String timestamp = Long.toString(System.currentTimeMillis() / 1000L); String signature = percentEncode(twitterSignature(status, nonce, timestamp, token, token_secret)); String auth = "OAuth " + "oauth_consumer_key=\"" + twitter_consumer_key + "\", " + "oauth_nonce=\"" + nonce + "\", " + "oauth_signature=\"" + signature + "\", " + "oauth_signature_method=\"HMAC-SHA1\", " + "oauth_timestamp=\"" + timestamp + "\", " + "oauth_token=\"" + token + "\", " + "oauth_version=\"1.0\""; HttpsURLConnection conn = (HttpsURLConnection) new URL(TWITTERURL).openConnection(); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("User-Agent", "Juick"); conn.setRequestProperty("Content-Length", Integer.toString(status.length() + 7)); conn.setRequestProperty("Authorization", auth); conn.setUseCaches(false); conn.setDoInput(true); conn.setDoOutput(true); conn.setRequestMethod("POST"); conn.connect(); OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream()); wr.write("status=" + status); wr.close(); ret = streamToString(conn.getInputStream()) != null; conn.disconnect(); } catch (Exception e) { logger.log(Level.SEVERE, "twitterPost: " + e.getMessage(), e); } return ret; } public String twitterSignature(String status, String nonce, String timestamp, String token, String token_secret) { try { // ALPHABET-SORTED String params = "oauth_consumer_key=" + twitter_consumer_key + "&oauth_nonce=" + nonce + "&oauth_signature_method=HMAC-SHA1" + "&oauth_timestamp=" + timestamp + "&oauth_token=" + token + "&oauth_version=1.0" + "&status=" + status; String base = "POST&" + percentEncode(TWITTERURL) + "&" + percentEncode(params); String key = twitter_consumer_secret + "&" + token_secret; Key signingKey = new SecretKeySpec(key.getBytes(), "HmacSHA1"); Mac mac = Mac.getInstance("HmacSHA1"); mac.init(signingKey); byte[] rawHmac = mac.doFinal(base.getBytes()); return Base64.encode(rawHmac); } catch (Exception e) { logger.log(Level.SEVERE, "twitterSignature: " + e.getMessage(), e); } return null; } public String streamToString(InputStream is) { try { BufferedReader buf = new BufferedReader(new InputStreamReader(is)); StringBuilder str = new StringBuilder(); String line; do { line = buf.readLine(); str.append(line).append("\n"); } while (line != null); return str.toString(); } catch (Exception e) { logger.log(Level.SEVERE, "streamToString: " + e.getMessage(), e); } return null; } public String getMessageHashTags(com.juick.Message jmsg) { String hashtags = ""; for (int i = 0; i < jmsg.Tags.size(); i++) { hashtags += "#" + jmsg.Tags.get(i) + " "; } return hashtags; } public static String percentEncode(String s) { String ret = ""; try { ret = URLEncoder.encode(s, "UTF-8").replace("+", "%20").replace("*", "%2A").replace("%7E", "~"); } catch (UnsupportedEncodingException e) { } return ret; } }