import groovy.json.JsonException import groovy.json.JsonSlurper import org.serviio.library.metadata.MediaFileType import org.serviio.library.online.* import sun.misc.BASE64Decoder import javax.script.ScriptEngine import javax.script.ScriptEngineManager import java.util.regex.Matcher import java.util.zip.GZIPInputStream /** * WebResource extractor plugin for SeasonVar.ru * * @author Georgy Baydjanov, Michael Mishalov * @version 19.0 */ class SeasonVar extends WebResourceUrlExtractor { final static VALID_WEB_RESOURCE_URL = '^(?:http?://)?(?:www\\.)?seasonvar.ru/.*' final static JS_SOURCE_REGEX =/(?s);eval\((.*?)\);<\/script>/ final static JS_INNER_SOURCE_REGEX =/(?s);eval\((.*?)\); \$\(/ final static PLAYLIST_REGEX =/(?s)pl[0-9]* =( |)"(.*?)";/ final static PLAYER_PHP_REGEX =/(?s)php",(.*?)\);\$/ final static PLAYER_PHP_REGEX2 =/(?s)player.php",(.*?), function/ final static ID_REGEX =/(?s)var id = "(.*?)";/ final static SECURE_MARK_REGEX =/(?s)var secureMark = "(.*?)";/ final static TITLE_REGEX = /(?s)(.*?)<\/title>/ final static SEASON_ID_REGEX = /(?s)html5play" idSeas="(.*?)">HTML5/ final static USER_AGENT = 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:18.0) Gecko/20100101 Firefox/18.0' private static final String PLAYER_PHP_URL = "http://seasonvar.ru/player.php" /*new*/ final static codecsMatrix = [codecA:[["b", "U", "N", "I", "Z", "o", "H", "V", "T", "w", "W", "D", "a", "f", "t", "Q", "9", "v", "g", "8", "x", "0", "1", "4", "c", "="], ["5", "L", "i", "f", "B", "v", "D", "Q", "9", "T", "a", "V", "o", "Y", "w", "J", "7", "M", "Z", "c", "d", "g", "y", "e", "u", "="], ["Z", "8", "i", "t", "b", "M", "U", "9", "z", "Y", "1", "s", "7", "c", "R", "J", "I", "l", "G", "a", "m", "k", "f", "4", "6", "="], ["g", "N", "8", "z", "a", "o", "3", "W", "x", "7", "2", "L", "t", "Q", "v", "e", "l", "w", "U", "5", "m", "s", "V", "D", "Y", "="], ["0", "t", "a", "d", "7", "J", "B", "H", "x", "Q", "y", "Z", "5", "I", "1", "v", "g", "b", "M", "z", "N", "L", "R", "c", "w", "="], ["W", "a", "u", "V", "d", "M", "8", "6", "7", "i", "H", "Y", "9", "5", "N", "s", "w", "D", "p", "l", "T", "Z", "x", "G", "m", "="], ["J", "p", "v", "n", "s", "R", "0", "3", "T", "m", "w", "u", "9", "x", "g", "a", "G", "L", "U", "X", "z", "t", "b", "7", "H", "="]], /*new*/ codecB:[["p", "z", "m", "X", "R", "J", "2", "Y", "d", "n", "B", "G", "u", "y", "L", "s", "l", "M", "i", "e", "6", "k", "7", "3", "5", "F"], ["s", "p", "6", "3", "N", "G", "8", "k", "0", "U", "t", "z", "H", "l", "x", "W", "m", "n", "R", "I", "2", "b", "X", "1", "4", "E"], ["d", "X", "x", "n", "2", "3", "v", "5", "L", "u", "D", "g", "H", "N", "W", "y", "w", "V", "B", "e", "o", "Q", "p", "0", "T", "O"], ["9", "1", "4", "Z", "I", "M", "6", "i", "X", "J", "d", "f", "T", "u", "G", "n", "y", "B", "c", "k", "R", "b", "p", "0", "H", "C"], ["u", "U", "f", "T", "k", "W", "X", "4", "8", "2", "9", "Y", "o", "D", "e", "n", "l", "s", "3", "G", "V", "p", "i", "m", "6", "F"], ["2", "B", "g", "v", "Q", "n", "z", "b", "3", "f", "y", "1", "J", "I", "k", "X", "0", "e", "L", "c", "R", "4", "o", "t", "U", "O"], ["f", "N", "W", "5", "e", "l", "V", "D", "y", "Z", "I", "i", "M", "o", "Q", "1", "B", "8", "2", "6", "c", "d", "4", "Y", "k", "C"]]] private static final String SERVER_URI = "http://seasonvar.ru" final static String TITLE_CUT = new String(new BASE64Decoder().decodeBuffer("INGB0LzQvtGC0YDQtdGC0Ywg0L7QvdC70LDQudC9")) final static String TRANSLATION_PARAM= "translation" final static String TRANSLATION_DEFAULT = "trans" final static String TRANSLATION_REGEX = /(transLostFilm|transAmedia|transBaibaKo|transAltPro|transHamster|transViruseProject|transAlexFilm|transNewStudio|transTo4kaTV|transColdFilm|trans%D0%A1%D1%83%D0%B1%D1%82%D0%B8%D1%82%D1%80%D1%8B|trans%D0%A1%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82%D0%BD%D1%8B%D0%B9|trans)/ @Override protected WebResourceContainer extractItems(URL url, int i) { def params = url.query!=null? url.query.split(" ").inject([:]){ params, param-> if(param.contains("=")){ def (key, value) = param.split('=').toList(); params[key] = value; } params }:[:]; List<WebResourceItem> items = [] String pageSource = openURL(url); String playlistURL; Matcher jsSource = pageSource=~JS_SOURCE_REGEX if(jsSource.size()>0) playlistURL = getPlaylistURL((jsSource[0]-null)[-1].toString().trim(), url); else{ playlistURL = getPlaylistURL(getSourceFromPlayer(pageSource,url)) } assert (playlistURL!=null); playlistURL = (params[TRANSLATION_PARAM]!=null)? playlistURL.replaceAll(TRANSLATION_REGEX, TRANSLATION_DEFAULT+params[TRANSLATION_PARAM]): playlistURL.replaceAll(TRANSLATION_REGEX, TRANSLATION_DEFAULT) String title= ((pageSource=~TITLE_REGEX)[0]-null)[-1].toString().trim(); String seasonID= ((pageSource=~SEASON_ID_REGEX)[0]-null)[-1].toString().trim(); title = title.substring(0,title.indexOf(TITLE_CUT)); title = title.replaceAll("с","c"); title = title.replaceAll("С","C"); title = new String(title.getBytes(),"UTF-8") String json = openURL(new URL(SERVER_URI +playlistURL)); if(!json.endsWith("}")) json = decrypt(json); def slurper = new JsonSlurper(); def playlist = slurper.parseText(json) ; items.addAll(fetchItems(playlist,seasonID)) ; return new WebResourceContainer(title:title, thumbnailUrl: "http://cdn.seasonvar.ru/oblojka/"+seasonID+".jpg", items: items) } /** * Retrieves playlist URL * @param obfuscatedStr Obfuscated JavaScript source * @return playlist URL */ private String getPlaylistURL(String obfuscatedStr, URL referer){ obfuscatedStr = "jsSource ="+obfuscatedStr ScriptEngineManager manager = new ScriptEngineManager() ScriptEngine engine = manager.getEngineByName("JavaScript") engine.eval(obfuscatedStr) obfuscatedStr = engine.get("jsSource") if (obfuscatedStr.contains("flashvars=")){ return getPlaylistURL(obfuscatedStr) }else{ if(obfuscatedStr.contains("secure")){ String pageSource = getSourceFromPlayer(obfuscatedStr, referer) return getPlaylistURL(((pageSource=~JS_INNER_SOURCE_REGEX)[0]-null)[-1].toString().trim(), referer); } obfuscatedStr = obfuscatedStr.substring(6,obfuscatedStr.length()-2); if(obfuscatedStr.contains("eval")){ obfuscatedStr = obfuscatedStr.substring(obfuscatedStr.indexOf("eval")+5) } return getPlaylistURL(obfuscatedStr,referer) } } private getPlaylistURL(String source){ return decrypt(((source=~PLAYLIST_REGEX)[0]-null)[-1].toString().trim()) } private String getSourceFromPlayer(String string, URL referer) { Matcher matcher = string =~ PLAYER_PHP_REGEX if(matcher.size()==0) matcher = string=~PLAYER_PHP_REGEX2 def playerLoaderJson = (matcher[0] - null)[-1].toString().trim() def slurper = new JsonSlurper(); def playerLoaderObj = [id:'', secure:'']; try { playerLoaderObj = slurper.parseText(playerLoaderJson); }catch (JsonException ignored){ playerLoaderObj.id = (((string=~ID_REGEX)[0]) - null)[-1].toString().trim() playerLoaderObj.secure = (((string=~SECURE_MARK_REGEX)[0]) - null)[-1].toString().trim() } postToURL(referer, "id=" + playerLoaderObj.id + "&type=flash" + "&secure=" + playerLoaderObj.secure) } /** * Finds all actual video links and titles in received data * @param data to search * @return List contains all founded items */ private List<WebResourceItem> fetchItems(data,seasonID){ List<WebResourceItem> items = []; if(data in Map){ def playlist = data.playlist if(playlist){ items.addAll(fetchItems(playlist,seasonID)) }else{ String link = data.file; String title = data.comment title = title.replaceAll("с","c"); title = title.replaceAll("С","C"); title=title.replaceAll("\\r\\n|\\r|\\n", " "); title=title.replaceAll("<br>", " - "); title = new String(title.getBytes(),"UTF-8"); items << new WebResourceItem(title: title, additionalInfo: ['link' :link,'thumbnailUrl': "http://cdn.seasonvar.ru/oblojka/"+seasonID+".jpg" ]) } }else if (data in ArrayList){ data.each{ items.addAll(fetchItems(it,seasonID)) } } return items; } @Override protected ContentURLContainer extractUrl(WebResourceItem webResourceItem, PreferredQuality preferredQuality) { return new ContentURLContainer(fileType: MediaFileType.VIDEO,thumbnailUrl:webResourceItem.additionalInfo.thumbnailUrl ,contentUrl: webResourceItem.additionalInfo.link); } private String decrypt(String input){ def codecPairs = [codecsMatrix.codecA, codecsMatrix.codecB].transpose(); for(def pair: codecPairs){ String output = decrypt(input,(ArrayList<String>)pair[0],(ArrayList<String>)pair[1]); if(output.endsWith(".xml")|| output.endsWith("}") || output.contains("list.xml?time=")) return output; } return null; } private String decrypt(String input,ArrayList<String> codecA, ArrayList<String> codecB) { String output = input; for(int i = 0; i<codecA.size();i++){ output = swap(codecB[i],codecA[i],output); } BASE64Decoder decoder = new BASE64Decoder(); output = new String(decoder.decodeBuffer(output),"UTF8"); return output; } private String swap(String first, String second, String input) { String output = input.replace(first, "___"); output = output.replace(second, first); output = output.replace("___", second); return output; } protected String openURL(URL url){ URLConnection conn = url.openConnection(); conn.setRequestProperty("Cookie","sva=lVe324PqsI24"); return conn.getInputStream().text } protected String postToURL(URL url, String postParams){ HttpURLConnection connection = (HttpURLConnection) new URL(PLAYER_PHP_URL).openConnection(); connection.setRequestMethod("POST"); connection.setRequestProperty("Host", "seasonvar.ru"); connection.setRequestProperty("Connection", "keep-alive"); connection.setRequestProperty("Content-Length", Integer.toString(postParams.length())); connection.setRequestProperty("Origin", SERVER_URI); connection.setRequestProperty("X-Requested-With", "XMLHttpRequest"); connection.setRequestProperty("User-Agent", USER_AGENT); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("Referer", url.toString()); connection.setRequestProperty("Accept-Encoding", "gzip,deflate"); connection.setRequestProperty("Accept", "text/html, */*; q=0.01"); connection.setRequestProperty("Cookie","sva=lVe324PqsI24"); connection.setDoOutput(true); connection.setDoInput(true); DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); wr.writeBytes(postParams); wr.flush(); wr.close(); // Read page conten Reader decoder = new InputStreamReader("gzip".equals(connection.getContentEncoding())? new GZIPInputStream(connection.getInputStream()): connection.getInputStream()); BufferedReader buffered = new BufferedReader(decoder); StringBuilder sb = new StringBuilder(); String inputLine; while ((inputLine = buffered.readLine()) != null) { sb.append(inputLine); } buffered.close(); return sb.toString(); } @Override boolean extractorMatches(URL url) { return url ==~ VALID_WEB_RESOURCE_URL } @Override int getVersion() { return 19; } @Override String getExtractorName() { return getClass().getName() } static void main(args) { //def testUrl = new URL("http://seasonvar.ru/serial-12151-Geroi_Vozrozhdenie_serial_2015-1-season.html?translation=LostFilm"); //def testUrl = new URL("http://seasonvar.ru/serial-13831-Killdzhojs-2-season.html"); def testUrl = new URL("http://seasonvar.ru/serial-13730-Bezmozglye.html"); //def testUrl = new URL("http://seasonvar.ru/serial-8881-Kuhnya-3-season.html"); SeasonVar extractor = new SeasonVar() ; println "PluginName : " + extractor.getExtractorName(); println "TestMatch : " + extractor.extractorMatches(testUrl); WebResourceContainer container = extractor.extractItems(testUrl, -1); container.getItems().each { println "URL : " + extractor.extractUrl(it, PreferredQuality.HIGH) } } }