diff --git a/app/src/main/java/com/github/catvod/api/QuarkApi.java b/app/src/main/java/com/github/catvod/api/QuarkApi.java index a51817ed..7bfbfb3f 100644 --- a/app/src/main/java/com/github/catvod/api/QuarkApi.java +++ b/app/src/main/java/com/github/catvod/api/QuarkApi.java @@ -1,326 +1,254 @@ +/* package com.github.catvod.api; +import com.github.catvod.bean.quark.Item; +import com.github.catvod.utils.Util; + +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class QuarkApi { - /* constructor() { - this.apiUrl = "https://drive-pc.quark.cn/1/clouddrive/" - this.cookie = "" - this.ckey = "" - this.shareTokenCache = {} - this.pr = "pr=ucpro&fr=pc" - this.subtitleExts = ['.srt', '.ass', '.scc', '.stl', '.ttml']; - this.saveFileIdCaches = {} - this.saveDirId = null - this.saveDirName = 'TV'; + private static final String API_URL = "https://drive-pc.QuarkApi.cn/1/clouddrive/"; + private String cookie; + private String ckey; + private Map shareTokenCache; + private String pr = "pr=ucpro&fr=pc"; + private List subtitleExts; + private Map saveFileIdCaches; + private String saveDirId; + private String saveDirName = "TV"; + + public QuarkApi() { + this.shareTokenCache = new HashMap<>(); + this.subtitleExts = List.of(".srt", ".ass", ".scc", ".stl", ".ttml"); + this.saveFileIdCaches = new HashMap<>(); } - async initQuark(cookie) { - this.ckey = Crypto.enc.Hex.stringify(Crypto.MD5(cookie)).toString(); - let localCfg = await local.get("quark", "cookie"); - if (!_.isEmpty(localCfg)) { - this.cookie = JSON.parse(localCfg)[this.ckey] + public static String bytesToHex(byte[] bytes) { + StringBuilder hexString = new StringBuilder(); + for (byte b : bytes) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } + + public void initQuark(String cookie) { + + this.ckey = bytesToHex(Util.MD5(cookie).getBytes()); + String localCfg = local.get("quark", "cookie"); + if (!localCfg.isEmpty()) { + Map cookieMap = new HashMap<>(); + JSONObject localCookie = new JSONObject(localCfg); + cookieMap.put(this.ckey, localCookie.getString(this.ckey)); + this.cookie = cookieMap.get(this.ckey); } else { - this.cookie = cookie + this.cookie = cookie; } } - private Map getHeaders() { - return Map.of( - "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch", - "Referer", "https://pan.quark.cn/", - "Content-Type", "application/json", - "Cookie", this.cookie, - "Host", "drive-pc.quark.cn" - ); - + public Map getHeaders() { + return Map.of("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch", "Referer", "https://pan.QuarkApi.cn/", "Content-Type", "application/json", "Cookie", this.cookie, "Host", "drive-pc.QuarkApi.cn"); } - async api(url, data, retry, method) { - const leftRetry = retry || 3; - let resp = await req(this.apiUrl + url, { - method:method || "post", - data:data, - headers:this.getHeaders() - }) - if (resp.headers['set-cookie']) { - const puus = [resp.headers['set-cookie']].join(';;;').match( / __puus = ([ ^;]+)/); - if (puus) { - if ( this.cookie.match( / __puus = ([ ^;]+)/)[1] !=puus[1]){ - this.cookie = this.cookie.replace( / __puus =[^;]+ /, `__puus = $ { - puus[1] - }`); - let cookieDic = {} - cookieDic[this.ckey] = this.cookie - await local.set("quark", this.cookie, JSON.stringify(cookieDic)); - } + public JSONObject api(String url, String data, int retry, String method) throws ExecutionException, InterruptedException { + int leftRetry = retry != null ? retry : 3; + JSONObject resp = req(API_URL + url, method != null ? method : "post", data, getHeaders()); + if (resp.headers().get("set-cookie") != null) { + String[] puus = resp.headers().get("set-cookie").split(";;;").stream().map(String::trim).filter(s -> s.startsWith("__puus=")).map(s -> s.substring(s.indexOf('=') + 1)).collect(Collectors.toList()).toArray(new String[0]); + if (puus.length > 0 && !this.cookie.matches("__puus=[^;]+") || !this.cookie.replace("__puus=[^;]+", "__puus=" + puus[0]).equals(this.cookie)) { + this.cookie = this.cookie.replace("__puus=[^;]+", "__puus=" + puus[0]); + Map cookieDic = new HashMap<>(); + cookieDic.put(this.ckey, this.cookie); + local.set("quark", this.cookie, new JSONObject(cookieDic).toString()); } } - if (resp.code != = 200 && leftRetry > 0) { - Utils.sleep(1) - return await this.api(url, data, leftRetry - 1); + if (resp.code() != 200 && leftRetry > 0) { + Utils.sleep(1); + return api(url, data, leftRetry - 1, method); } - return JSON.parse(resp.content) || {}; + return resp; } - getShareData(url) { - let regex = /https:\/\/pan\.quark\.cn\/s\/([ ^\\|#/]+)/; - let matches = regex.exec(url); - if (matches) { - return { - shareId:matches[1], - folderId:'0', - }; + public Map getShareData(String url) { + Pattern regex = Pattern.compile("https://pan\\.quark\\.cn\\/s\\/([^\\|#/]+)"); + Matcher matches = regex.matcher(url); + if (matches.find()) { + return Map.of("shareId", matches.group(1), "folderId", "0"); } return null; } - async getShareToken(shareData) { - if (!this.shareTokenCache[shareData.shareId]) { - delete this.shareTokenCache[shareData.shareId]; - const shareToken = await this.api(`share / sharepage / token ? $ { - this.pr - }`,{ - pwd_id: - shareData.shareId, - passcode:shareData.sharePwd || '', - }); - if (shareToken.data && shareToken.data.stoken) { - this.shareTokenCache[shareData.shareId] = shareToken.data; + public void getShareToken(QuarkApi.ShareData shareData) throws ExecutionException, InterruptedException { + if (!shareTokenCache.containsKey(shareData.shareId)) { + JSONObject shareToken = api("share/sharepage/token?" + pr, Map.of("pwd_id", shareData.shareId, "passcode", shareData.sharePwd), null, "get"); + if (shareToken != null && shareToken.has("data") && shareToken.getJSONObject("data").has("stoken")) { + shareTokenCache.put(shareData.shareId, shareToken.getJSONObject("data").getJSONObject("stoken").toString()); } } } - async listFile(shareIndex, shareData, videos, subtitles, shareId, folderId, page) { - const prePage = 200; - page = page || 1; - const listData = await this.api(`share / sharepage / detail ? $ { - this.pr - }&pwd_id = $ { - shareId - }&stoken = $ { - encodeURIComponent(this.shareTokenCache[shareId].stoken) - }&pdir_fid = $ { - folderId - }&force = 0 & _page = $ { - page - }&_size = $ { - prePage - }&_sort = file_type:asc, file_name:asc`,null, null, 'get'); - if (!listData.data) return []; - const items = listData.data.list; - if (!items) return []; - const subDir = []; - for (const item of items){ - if (item.dir == = true) { - subDir.push(item); - } else if (item.file == = true && item.obj_category == = 'video') { - if (item.size < 1024 * 1024 * 5) continue; - item.stoken = this.shareTokenCache[shareData.shareId].stoken; - videos.push(Item.objectFrom(item, shareData.shareId, shareIndex)); - } else if (item.type == = 'file' && this.subtitleExts.some((x) = > item.file_name.endsWith(x))) - { - subtitles.push(Item.objectFrom(item, shareData, shareIndex)); + public List listFile(QuarkApi.ShareData shareData, List videos, List subtitles, String shareId, String folderId, int page) throws ExecutionException, InterruptedException { + int prePage = 200; + page = page != 0 ? page : 1; + JSONObject listData = api("share/sharepage/detail?" + pr + "&pwd_id=" + shareId + "&stoken=" + URLEncoder.encode(shareTokenCache.get(shareData.shareId).get("stoken")) + "&pdir_fid=" + folderId + "&force=0&_page=" + page + "&_size=" + prePage + "&_sort=file_type:asc,file_name:asc", null, null, "get"); + if (listData != null && listData.has("data") && listData.getJSONObject("data").has("list")) { + List items = listData.getJSONObject("data").getJSONArray("list").toList().stream().map(item -> Item.objectFrom(item, shareData, page)).collect(Collectors.toList()); + if (page < Math.ceil(listData.getJSONObject("metadata").getJSONObject("_total").toString() / prePage)) { + List nextItems = listFile(shareData, videos, subtitles, shareId, folderId, page + 1); + items.addAll(nextItems); + } + for (Item dir : items) { + if (dir.dir()) { + List subItems = listFile(shareData, videos, subtitles, shareId, dir.fid); + items.addAll(subItems); + } + } + return items; + } + return new ArrayList<>(); + } + + public JSONObject findBestLCS(Item mainItem, List targetItems) { + List results = targetItems.stream().map(targetItem -> new JSONObject().put("target", targetItem).put("lcs", Utils.lcs(mainItem.name, targetItem.name))).collect(Collectors.toList()); + int bestMatchIndex = 0; + for (int i = 1; i < results.size(); i++) { + if (results.get(i).getJSONObject("lcs").length() > results.get(bestMatchIndex).getJSONObject("lcs").length()) { + bestMatchIndex = i; } } - if (page < Math.ceil(listData.metadata._total / prePage)) { - const nextItems = await - this.listFile(shareIndex, shareData.shareId, videos, subtitles, shareId, folderId, page + 1); - for (const item of nextItems){ - items.push(item); - } - } - for (const dir of subDir){ - const subItems = await - this.listFile(shareIndex, shareData, videos, subtitles, shareId, dir.fid); - for (const item of subItems){ - items.push(item); - } + return results.get(bestMatchIndex); + } + + public List getFilesByShareUrl(int shareIndex, QuarkApi.ShareData shareInfo, List videos, List subtitles) throws ExecutionException, InterruptedException { + QuarkApi.ShareData shareData = shareInfo instanceof String ? getShareData((String) shareInfo) : (QuarkApi.ShareData) shareInfo; + if (shareData == null) return Collections.emptyList(); + getShareToken(shareData); + if (shareTokenCache.get(shareData.shareId) == null) return Collections.emptyList(); + List items = listFile(shareIndex, shareData, videos, subtitles, shareData.shareId, shareData.folderId); + if (subtitles.size() > 0) { + videos.forEach(item -> { + JSONObject matchSubtitle = findBestLCS(item, subtitles); + if (matchSubtitle != null) { + item.subtitle = matchSubtitle.getJSONObject("target"); + } + }); } return items; } - ; - - findBestLCS(mainItem, targetItems) { - const results = []; - let bestMatchIndex = 0; - for (let i = 0; i < targetItems.length; i++) { - const currentLCS = Utils.lcs(mainItem.name, targetItems[i].name); - results.push({target:targetItems[i], lcs:currentLCS }); - if (currentLCS.length > results[bestMatchIndex].lcs.length) { - bestMatchIndex = i; - } - } - const bestMatch = results[bestMatchIndex]; - return {allLCS:results, bestMatch:bestMatch, bestMatchIndex:bestMatchIndex }; - } - - async getFilesByShareUrl(shareIndex, shareInfo, videos, subtitles) { - const shareData = typeof shareInfo == = 'string' ? this.getShareData(shareInfo) : shareInfo; - if (!shareData) return []; - await this.getShareToken(shareData); - if (!this.shareTokenCache[shareData.shareId]) return []; - await - this.listFile(shareIndex, shareData, videos, subtitles, shareData.shareId, shareData.folderId); - if (subtitles.length > 0) { - videos.forEach((item) = > { - var matchSubtitle = this.findBestLCS(item, subtitles); - if (matchSubtitle.bestMatch) { - item.subtitle = matchSubtitle.bestMatch.target; - } - }); + public void clean() { + Map saves = new HashMap<>(saveFileIdCaches); + for (String save : saves.keySet()) { + saveFileIdCaches.remove(save); } } - clean() { - const saves = Object.keys(this.saveFileIdCaches); - for (const save of saves){ - delete this.saveFileIdCaches[save]; + public void clearSaveDir() throws ExecutionException, InterruptedException { + JSONObject listData = api("file/sort?" + pr + "&pdir_fid=" + saveDirId + "&_page=1&_size=200&_sort=file_type:asc,updated_at:desc", null, null, "get"); + if (listData != null && listData.has("data") && listData.getJSONObject("data").has("list") && listData.getJSONObject("data").getJSONArray("list").length() > 0) { + api("file/delete?" + pr, Map.of("action_type", 2, "filelist", listData.getJSONObject("data").getJSONArray("list").toList().stream().map(f -> f.getString("fid")).collect(Collectors.toList()), "exclude_fids", new JSONArray())); } } - - async clearSaveDir() { - const listData = await this.api(`file / sort ? $ { - this.pr - }&pdir_fid = $ { - this.saveDirId - }&_page = 1 & _size = 200 & _sort = file_type:asc, updated_at:desc`,{ - },{ - },'get'); - if (listData.data && listData.data.list && listData.data.list.length > 0) { - await this.api(`file / delete ? $ { - this.pr - }`,{ - action_type: - 2, - filelist:listData.data.list.map((v) = > v.fid), - exclude_fids: [], - }); - } - } - - async createSaveDir(clean) { - if (this.saveDirId) { - // 删除所有子文件 - if (clean) await this.clearSaveDir(); + public void createSaveDir(boolean clean) throws ExecutionException, InterruptedException { + if (saveDirId != null) { + if (clean) clearSaveDir(); return; } - const listData = await this.api(`file / sort ? $ { - this.pr - }&pdir_fid = 0 & _page = 1 & _size = 200 & _sort = file_type:asc, updated_at:desc`,{ - },{ - },'get'); - if (listData.data && listData.data.list) - for (const item of listData.data.list){ - if (item.file_name == = this.saveDirName) { - this.saveDirId = item.fid; - await this.clearSaveDir(); - break; + JSONObject listData = api("file/sort?" + pr + "&pdir_fid=0&_page=1&_size=200&_sort=file_type:asc,updated_at:desc", null, null, "get"); + if (listData != null && listData.has("data") && listData.getJSONObject("data").has("list")) { + for (JSONObject item : listData.getJSONObject("data").getJSONArray("list").toList()) { + if (item.getString("file_name").equals(saveDirName)) { + saveDirId = item.getString("fid"); + clearSaveDir(); + break; + } } } - if (!this.saveDirId) { - const create = await this.api(`file ? $ { - this.pr - }`,{ - pdir_fid: - '0', - file_name:this.saveDirName, - dir_path:'', - dir_init_lock:false, - }); - if (create.data && create.data.fid) { - this.saveDirId = create.data.fid; + if (saveDirId == null) { + JSONObject create = api("file?" + pr, Map.of("pdir_fid", "0", "file_name", saveDirName, "dir_path", "", "dir_init_lock", false)); + if (create != null && create.has("data") && create.getJSONObject("data").has("fid")) { + saveDirId = create.getJSONObject("data").getString("fid"); } } } - async save(shareId, stoken, fileId, fileToken, clean) { - await this.createSaveDir(clean); - if (clean) { - this.clean() + public String save(String shareId, String stoken, String fileId, String fileToken, boolean clean) throws ExecutionException, InterruptedException { + createSaveDir(clean); + if (clean) clean(); + if (saveDirId == null) return null; + if (stoken == null) { + getShareToken(Map.of("shareId", shareId)); + if (shareTokenCache.get(shareId) == null) return null; } - if (!this.saveDirId) return null; - if (!stoken) { - await this.getShareToken({ - shareId:shareId, - }); - if (!this.shareTokenCache[shareId]) return null; - } - const saveResult = await this.api(`share / sharepage / save ? $ { - this.pr - }`,{ - fid_list: [fileId], - fid_token_list: [fileToken], - to_pdir_fid: - this.saveDirId, - pwd_id:shareId, - stoken:stoken || this.shareTokenCache[shareId].stoken, - pdir_fid:'0', - scene:'link', - }); - if (saveResult.data && saveResult.data.task_id) { - let retry = 0; - // wait task finish + JSONObject saveResult = api("share/sharepage/save?" + pr, Map.of("fid_list", new JSONArray().put(fileId), "fid_token_list", new JSONArray().put(fileToken), "to_pdir_fid", saveDirId, "pwd_id", shareId, "stoken", stoken != null ? stoken : shareTokenCache.get(shareId).getJSONObject("stoken").toString(), "pdir_fid", "0", "scene", "link")); + if (saveResult != null && saveResult.has("data") && saveResult.getJSONObject("data").has("task_id")) { + int retry = 0; while (true) { - const taskResult = await this.api(`task ? $ { - this.pr - }&task_id = $ { - saveResult.data.task_id - }&retry_index = $ { - retry - }`,{ - },{ - },'get'); - if (taskResult.data && taskResult.data.save_as && taskResult.data.save_as.save_as_top_fids && taskResult.data.save_as.save_as_top_fids.length > 0) { - return taskResult.data.save_as.save_as_top_fids[0]; + JSONObject taskResult = api("task?" + pr + "&task_id=" + saveResult.getJSONObject("data").getString("task_id") + "&retry_index=" + retry, null, null, "get"); + if (taskResult != null && taskResult.has("data") && taskResult.getJSONObject("data").has("save_as") && taskResult.getJSONObject("data").getJSONObject("save_as").has("save_as_top_fids") && taskResult.getJSONObject("data").getJSONObject("save_as").getJSONArray("save_as_top_fids").length() > 0) { + return taskResult.getJSONObject("data").getJSONObject("save_as").getJSONArray("save_as_top_fids").getJSONObject(0).getString("fid"); } retry++; if (retry > 2) break; - Utils.sleep(1); + TimeUnit.SECONDS.sleep(1); } } - return false; + return "false"; } - - async getLiveTranscoding(shareId, stoken, fileId, fileToken) { - if (!this.saveFileIdCaches[fileId]) { - const saveFileId = await this.save(shareId, stoken, fileId, fileToken, true); - if (!saveFileId) return null; - this.saveFileIdCaches[fileId] = saveFileId; + public List getLiveTranscoding(String shareId, String stoken, String fileId, String fileToken) throws ExecutionException, InterruptedException { + if (!saveFileIdCaches.containsKey(fileId)) { + String saveFileId = save(shareId, stoken, fileId, fileToken, true); + if (saveFileId == null) return null; + saveFileIdCaches.put(fileId, saveFileId); } - const transcoding = await this.api(`file / v2 / play ? $ { - this.pr - }`,{ - fid: - this.saveFileIdCaches[fileId], - resolutions:'normal,low,high,super,2k,4k', - supports:'fmp4', - }); - if (transcoding.data && transcoding.data.video_list) { - return transcoding.data.video_list; + JSONObject transcoding = api("file/v2/play?" + pr, Map.of("fid", saveFileIdCaches.get(fileId), "resolutions", "normal,low,high,super,2k,4k", "supports", "fmp4")); + if (transcoding != null && transcoding.has("data") && transcoding.getJSONObject("data").has("video_list")) { + return transcoding.getJSONObject("data").getJSONArray("video_list").toList(); } return null; } - async getDownload(shareId, stoken, fileId, fileToken, clean) { - if (!this.saveFileIdCaches[fileId]) { - const saveFileId = await this.save(shareId, stoken, fileId, fileToken, clean); - if (!saveFileId) return null; - this.saveFileIdCaches[fileId] = saveFileId; + public JSONObject getDownload(String shareId, String stoken, String fileId, String fileToken, boolean clean) throws ExecutionException, InterruptedException { + if (!saveFileIdCaches.containsKey(fileId)) { + String saveFileId = save(shareId, stoken, fileId, fileToken, clean); + if (saveFileId == null) return null; + saveFileIdCaches.put(fileId, saveFileId); } - const down = await this.api(`file / download ? $ { - this.pr - }&uc_param_str =`,{ - fids: [this.saveFileIdCaches[fileId]], - }); - if (down.data) { - return down.data[0]; + JSONObject down = api("file/download?" + pr + "&uc_param_str", Map.of("fids", new JSONArray().put(saveFileIdCaches.get(fileId)))); + if (down != null && down.has("data")) { + return down.getJSONObject("data").getJSONObject(0); } return null; } -*/ + + public static class ShareData { + private String shareId; + private String folderId; + private String sharePwd; + + public ShareData(String shareId, String folderId, String sharePwd) { + this.shareId = shareId; + this.folderId = folderId; + this.sharePwd = sharePwd; + } + } + + } + +*/ diff --git a/app/src/main/java/com/github/catvod/bean/quark/Item.java b/app/src/main/java/com/github/catvod/bean/quark/Item.java new file mode 100644 index 00000000..f5e6d096 --- /dev/null +++ b/app/src/main/java/com/github/catvod/bean/quark/Item.java @@ -0,0 +1,100 @@ +package com.github.catvod.bean.quark; + +import java.util.regex.Pattern; + +public class Item { + private String fileId; + private String shareId; + private String shareToken; + private String shareFileToken; + private String seriesId; + private String name; + private String type; + private String formatType; + private String size; + private String parent; + private String shareData; + private int shareIndex; + private long lastUpdateAt; + + public Item() { + this.fileId = ""; + this.shareId = ""; + this.shareToken = ""; + this.shareFileToken = ""; + this.seriesId = ""; + this.name = ""; + this.type = ""; + this.formatType = ""; + this.size = ""; + this.parent = ""; + this.shareData = null; + this.shareIndex = 0; + this.lastUpdateAt = 0; + } + + public static Item objectFrom(String item_json, String shareId, int shareIndex) { + Item item = new Item(); + item.fileId = item_json.contains("fid") ? item_json.split("fid=")[1].split("&")[0] : ""; + item.shareId = shareId; + item.shareToken = item_json.contains("stoken") ? item_json.split("stoken=")[1].split("&")[0] : ""; + item.shareFileToken = item_json.contains("share_fid_token") ? item_json.split("share_fid_token=")[1].split("&")[0] : ""; + item.seriesId = item_json.contains("series_id") ? item_json.split("series_id=")[1].split("&")[0] : ""; + item.name = item_json.contains("file_name") ? item_json.split("file_name=")[1].split("&")[0] : ""; + item.type = item_json.contains("obj_category") ? item_json.split("obj_category=")[1].split("&")[0] : ""; + item.formatType = item_json.contains("format_type") ? item_json.split("format_type=")[1].split("&")[0] : ""; + item.size = item_json.contains("size") ? item_json.split("size=")[1].split("&")[0] : ""; + item.parent = item_json.contains("pdir_fid") ? item_json.split("pdir_fid=")[1].split("&")[0] : ""; + item.lastUpdateAt = item_json.contains("last_update_at") ? Long.parseLong(item_json.split("last_update_at=")[1].split("&")[0]) : 0; + item.shareIndex = shareIndex; + return item; + } + + public String getFileExtension() { + String[] arr = name.split("\\."); + return arr[arr.length - 1]; + } + + public String getFileId() { + return fileId.isEmpty() ? "" : fileId; + } + + public String getName() { + return name.isEmpty() ? "" : name; + } + + public String getParent() { + return parent.isEmpty() ? "" : "[" + parent + "]"; + } + + public String getSize() { + return size.equals("0") ? "" : "[" + size + "]"; + } + + + public int getShareIndex() { + return shareIndex; + } + + public String getDisplayName(String type_name) { + String name = getName(); + if (type_name.equals("电视剧")) { + String[] replaceNameList = {"4k", "4K"}; + name = name.replaceAll("\\." + getFileExtension(), ""); + for (String replaceName : replaceNameList) { + name = name.replaceAll(replaceName, ""); + } + name = Pattern.compile("/\\.S01E(.*?)\\./").matcher(name).find() ? name.split("/\\.S01E(.*?)\\./")[1] : name; + String[] numbers = name.split("\\d+"); + if (numbers.length > 0) { + name = numbers[0]; + } + } + return name + " " + size; + } + + public String getEpisodeUrl(String type_name) { + return getDisplayName(type_name) + "$" + getFileId() + "++" + shareFileToken + "++" + shareId + "++" + shareToken; + } +} +