import groovy.json.JsonSlurper import org.serviio.library.metadata.MediaFileType import org.serviio.library.online.* import javax.net.ssl.HttpsURLConnection import javax.script.ScriptEngine import javax.script.ScriptEngineManager /** * WebResource extractor plugin for Google+ * * @author Michael Mishalov * @version 2.0 */ class GooglePlus extends WebResourceUrlExtractor{ protected final static VALID_WEB_RESOURCE_URL = '^(?:https?://)?(?:www\\.)?plus.google.com/.*' protected final static USER_AGENT = 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.186 Safari/535.1' protected final static USER_ID_REGEX = /(?s)photos\/(.*?)\/albums\// protected final static USER_ALBUMS_REGEX = /(?s)_user.albums =(.*?);/ protected final static USER_NUMERIC_ID_REGEX = /(?s)token="photos\/(.*?)\/albums/ protected final static NAME_ATR_REGEX = /(?s)name="(.*?)"/ protected final static VALUE_ATR_REGEX = /(?s)value="(.*?)"/ protected final static AUTH_FORM_REGEX = /(?s)
/ protected final static FEED_PRELOAD_REGEX = /(?s)feedPreload: (.*?),\{NEW_HOMEPAGE/ protected final static ALBUM_ID_SLIT = "albums/" protected final static USER_GALLERY_URL = "https://picasaweb.google.com/%s?noredirect=1" protected static final int PLUGIN_VERSION = 2 protected static final JSON_CONVERT_PATTERN = "JSON.stringify(%s);" public static final String AUTH_URL = "https://accounts.google.com/ServiceLoginAuth" private List cookies; private HttpsURLConnection connection; @Override protected WebResourceContainer extractItems(URL url, int i) { // make sure cookies is turn on CookieHandler.setDefault(new CookieManager()); String[] split = url.toString().split(" ") String userId = ((split[0]=~USER_ID_REGEX)[0]-null)[-1].toString().trim() String albumId = split[0].split(ALBUM_ID_SLIT)[1]; String username = null String password = null if(split.size()==2){ split=split[1].split(",") username = split[0].split("=")[1] password = split[1].split("=")[1] } if(username!=null && password ){ authenticate(username,password) } if(!userId.isNumber()){ String pageSource = getPageContent(url.toString().split(" ")[0]) userId = ((pageSource=~USER_NUMERIC_ID_REGEX)[0]-null)[-1].toString().trim() } String pageSource = getPageContent(String.format(USER_GALLERY_URL,userId)) String json = ((pageSource=~USER_ALBUMS_REGEX)[0]-null)[-1].toString().trim() String jsSource = "json ="+ String.format(JSON_CONVERT_PATTERN,json); ScriptEngineManager manager = new ScriptEngineManager() ScriptEngine engine = manager.getEngineByName("JavaScript") engine.eval(jsSource); json = engine.get("json"); def allAlbums = new JsonSlurper().parseText(json) String webResourceTitle="Unknown title" String thumbnailUrl=""; List items = [] def album = allAlbums.find{albumData -> albumData.id==albumId } if(album!=null){ webResourceTitle=album.title thumbnailUrl=album.src items.addAll(fetchItems(album)) } return new WebResourceContainer(title: webResourceTitle ,thumbnailUrl:thumbnailUrl, items: items) } /** * Authenticate to G++ using username and password * */ private void authenticate(String username, String password) throws Exception { String pageContent = getPageContent(AUTH_URL) String postParams = buildPostParams(pageContent,username, password) connection = (HttpsURLConnection) new URL(AUTH_URL).openConnection(); connection.setUseCaches(false); connection.setRequestMethod("POST"); connection.setRequestProperty("Host", "accounts.google.com"); connection.setRequestProperty("User-Agent", USER_AGENT); connection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); connection.setRequestProperty("Accept-Language", "en-US,en;q=0.5"); if (cookies != null) { this.cookies.each { connection.addRequestProperty("Cookie", it.split(";", 1)[0]); } } connection.setRequestProperty("Connection", "keep-alive"); connection.setRequestProperty("Referer", AUTH_URL); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("Content-Length", Integer.toString(postParams.length())); connection.setDoOutput(true); connection.setDoInput(true); // Send post request DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); wr.writeBytes(postParams); wr.flush(); wr.close(); // Read page content to get authentication String ignored = connection.getInputStream().text } /** * Get page content and keep cookies * */ private String getPageContent(String url) throws Exception { connection = (HttpsURLConnection) new URL(url).openConnection(); connection.setRequestMethod("GET"); connection.setUseCaches(false); connection.setRequestProperty("User-Agent", USER_AGENT); connection.setRequestProperty("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); connection.setRequestProperty("Accept-Language", "en-US,en;q=0.5"); if (cookies != null) { this.cookies.each { connection.addRequestProperty("Cookie", it.split(";", 1)[0]); } } int responseCode = connection.getResponseCode(); String response = (responseCode==200)? connection.getInputStream().text:"" cookies = connection.getHeaderFields().get("Set-Cookie") response } private String buildPostParams(String http, String username, String password) { String formHtml = ((http=~AUTH_FORM_REGEX)[0]-null)[-1].toString().trim() formHtml = formHtml.replaceAll("\\r\\n|\\r|\\n", " ").replaceAll("'","\"") def paramList = [] for (String inputElement:formHtml.split("input") ) { if(inputElement.contains("name") && inputElement.contains("value")){ String name = ((inputElement=~NAME_ATR_REGEX)[0]-null)[-1].toString().trim() String value = ((inputElement=~VALUE_ATR_REGEX)[0]-null)[-1].toString().trim() if (name.equals("Email")) value = username; paramList << (name + "=" + URLEncoder.encode(value, "UTF-8")); } } paramList << ("Passwd=" + URLEncoder.encode(password, "UTF-8")); return paramList.join("&") } @Override protected ContentURLContainer extractUrl(WebResourceItem webResourceItem, PreferredQuality preferredQuality) { return new ContentURLContainer(fileType: MediaFileType.IMAGE,thumbnailUrl:webResourceItem.additionalInfo.thumbnailURL, contentUrl: webResourceItem.additionalInfo.sourceURL) } private List fetchItems(album){ List items = []; String pageSource = getPageContent(album.url).replaceAll("\\r\\n|\\r|\\n", "") String json = ((pageSource=~FEED_PRELOAD_REGEX)[0]-null)[-1].toString().trim() def feedPreload = new JsonSlurper().parseText(json) feedPreload.feed.entry.each{ try{ String width = it.media.thumbnail[-1].width String thumbnailURL = it.media.thumbnail[-1].url String clearURL = thumbnailURL.substring(0,thumbnailURL.indexOf("s"+width+"/"+it.title)) String sourceURL = clearURL+"s"+it.width+"/"+it.title items << new WebResourceItem(title: it.title, additionalInfo:['sourceURL':sourceURL,'thumbnailURL':thumbnailURL]) }catch (IndexOutOfBoundsException ignored){} } return items; } @Override boolean extractorMatches(URL url) { return url ==~ VALID_WEB_RESOURCE_URL } @Override String getExtractorName() { return getClass().getName() } @Override int getVersion() { return PLUGIN_VERSION; } static void main(args) { def TestUrl = new URL("https://plus.google.com/u/0/photos/107982474099703846460/albums/5924047856490871153") GooglePlus extractor = new GooglePlus() println "PluginName : " + extractor.getExtractorName(); println "TestMatch : " + extractor.extractorMatches(TestUrl); WebResourceContainer container = extractor.extractItems(TestUrl, -1); container.getItems().each { println it.title + ", URL : " + extractor.extractUrl(it, PreferredQuality.HIGH).contentUrl; } } }