package com.yutou.qqbot.models.BiliBili; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.yutou.napcat.enums.MessageEnum; import com.yutou.napcat.event.MessageEvent; import com.yutou.napcat.handle.OtherHandle; import com.yutou.napcat.handle.Reply; import com.yutou.napcat.http.NapCatApi; import com.yutou.napcat.model.MessageBean; import com.yutou.okhttp.HttpBody; import com.yutou.qqbot.Annotations.UseModel; import com.yutou.qqbot.QQBotManager; import com.yutou.qqbot.bilibili.*; import com.yutou.qqbot.interfaces.ObjectInterface; import com.yutou.qqbot.models.Model; import com.yutou.qqbot.utlis.AppTools; import com.yutou.qqbot.utlis.ConfigTools; import com.yutou.qqbot.utlis.HttpTools; import com.yutou.qqbot.utlis.StringUtils; import retrofit2.Response; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @UseModel public class BiliVideo extends Model { public String downloadPath = "tmp"; List danmuDatas = new ArrayList<>(); long danmuNextTime = 0; private BiliBiliUtils biliUtils; private long qq; public BiliVideo(long qq) { this.qq = qq; biliUtils = BiliBiliUtils.getInstance(qq); } public BiliVideo() { this.qq = QQBotManager.defQQ; biliUtils = BiliBiliUtils.getInstance(qq); } @Override public boolean isUserPublic() { return true; } @Override public String[] getUsePowers() { return new String[0]; } @Override public String getModelName() { return "B站视频下载"; } @Override public void onMessage(Long qq, MessageEvent event, boolean isGroup) { super.onMessage(qq, event, isGroup); if (event.isAtMe() && event.hasType(MessageEnum.REPLY)) { Reply reply = event.findType(Reply.class); long id = reply.getData().getId(); if (msg.contains("省流") || msg.contains("总结")) { String value = onAIVideo(id); if (!StringUtils.isEmpty(value)) { QQBotManager.getInstance().sendMessage(qq, value); } } } } private String onAIVideo(long id) { try { Response> execute = NapCatApi.getMessageApi().getMessage(id).execute(); if(execute.body()==null){ return "省流失败"; } MessageEvent handle = MessageEvent.parseHandleHttp(execute.body().getSrc()); if (handle.hasType(MessageEnum.JSON)) { OtherHandle type = handle.findType(OtherHandle.class); String url = type.getData().getMeta().getDetail1().getQqdocurl(); if (StringUtils.isEmpty(url)) { return "地址不正确"; } if (url.startsWith("BV")) { url = "https://www.bilibili.com/video/" + url.trim(); } if (!url.startsWith("https://www.bilibili.com/video/") && !url.startsWith("https://b23.tv")) { return "这是B站吗?"; } String ai = BiliBiliAppUtils.getVideoAI(url.trim()); if (!StringUtils.isEmpty(ai)) { return ai; } } } catch (Exception e) { e.printStackTrace(); } return "省流失败"; } public void downVideo(String url) { downVideo(url, true, false); } public void downVideo(String url, boolean downDanmu, boolean merge) { if (biliUtils == null || !new BiliLogin(qq).testLogin()) { System.err.println("未登录"); return; } if (!url.contains("?")) { url += "?"; } danmuDatas.clear(); JSONObject info = getVideoInfo(url); if (info.getInteger("code") == 0) { JSONObject infoData = info.getJSONObject("data"); JSONObject eps = new JSONObject(); if (infoData.containsKey("ugc_season")) { JSONObject ugc = infoData.getJSONObject("ugc_season"); eps.put("title", ugc.getString("title")); JSONArray ep = new JSONArray(); for (Object o : ugc.getJSONArray("sections")) { JSONObject season = (JSONObject) o; for (Object episodes : season.getJSONArray("episodes")) { JSONObject _epi = (JSONObject) episodes; JSONObject _item = new JSONObject(); _item.put("title", season.getString("title") + "-" + _epi.getString("title")); _item.put("cid", _epi.getLong("cid")); _item.put("aid", _epi.getLong("aid")); ep.add(_item); } } eps.put("eps", ep); } else if (infoData.getInteger("videos") != 1) { JSONArray pages = infoData.getJSONArray("pages"); JSONArray ep = new JSONArray(); for (Object o : pages) { JSONObject page = (JSONObject) o; JSONObject _item = new JSONObject(); _item.put("title", infoData.getString("title") + "-" + page.getString("part")); _item.put("cid", page.getLong("cid")); _item.put("aid", infoData.getLong("aid")); ep.add(_item); } eps.put("title", infoData.getString("title")); eps.put("eps", ep); } else { eps.put("title", infoData.getString("title")); eps.put("cid", infoData.getLong("cid")); } JSONObject json = new JSONObject(); json.put("danmu", downDanmu); json.put("merge", merge); json.put("avid", infoData.getLong("aid")); if (eps.containsKey("cid")) { json.put("cid", eps.getLong("cid")); json.put("qn", 127); json.put("fnval", 80); json.put("fourk", 1); downVideo(json, eps); } else { System.out.println("json = " + json); System.out.println("eps = " + eps); List list = new ArrayList<>(); String root = new File("tmp").getAbsolutePath() + File.separator; for (Object o : eps.getJSONArray("eps")) { JSONObject item = (JSONObject) o; json.put("avid", item.getLong("aid")); json.put("cid", item.getLong("cid")); json.put("qn", 127); json.put("fnval", 80); json.put("fourk", 1); item.put("title", eps.getString("title") + "$(File.separator)" + item.getString("title")); list.add(new File(root + StringUtils.toSaveFileName(item.getString("title") + ".mp4"))); downVideo(json, item); if (downDanmu && merge) { long tmp = 0; for (VideoDanMu.DanmakuElem elem : buildDanmuHttp(json.getLong("cid"), json.getLong("avid"), 1)) { DanmuData danmuData = new DanmuData(); danmuData.setDanmu(elem.getContent()); danmuData.setFontSize(elem.getFontsize()); danmuData.setTime(elem.getProgress() + danmuNextTime); danmuData.setFontColor(elem.getColor()); danmuData.setModel(elem.getMode()); if (elem.getProgress() > tmp) { tmp = elem.getProgress(); } danmuDatas.add(danmuData); } danmuNextTime = tmp; } } if (merge) { merge(root, StringUtils.toSaveFileName(eps.getString("title")), list); } } } } private void merge(String root, String name, List files) { String saveName = root + name; File fileList = new File(saveName + File.separator + "tmp.txt"); System.out.println("fileList.getAbsolutePath() = " + fileList.getAbsolutePath()); StringBuilder builder = new StringBuilder(); int i = 0; List tmp = new ArrayList<>(); for (File file : files) { System.out.println("file.getName() = " + file.getName()); file.renameTo(new File(file.getParentFile(), i + ".mp4")); tmp.add(new File(file.getParentFile(), i + ".mp4")); builder.append("file '").append(i++).append(".mp4'"); builder.append("\n"); } try { // boolean b = fileList.createNewFile(); // System.out.println("b = " + b); FileWriter fw = new FileWriter(fileList); fw.write(builder.toString()); fw.flush(); fw.close(); String exec = String.format("cd \"%s\" && ffmpeg -f concat -i \"%s\" -c copy %s.mp4", saveName, "tmp.txt", name); System.out.println("exec = " + exec); AppTools.exec(exec, new ObjectInterface() { @Override public void out(String data) { super.out(data); // System.out.println(data); System.out.println("over"); fileList.delete(); for (File file : tmp) { file.delete(); } AssTools tools = new AssTools(name); tools.addDanmu(danmuDatas); boolean saveDanmu = tools.saveDanmu(saveName); System.out.println("弹幕保存:" + saveDanmu); } }, false, false); } catch (IOException e) { throw new RuntimeException(e); } } private void downVideo(JSONObject json, JSONObject eps) { eps.put("title", StringUtils.toSaveFileName(eps.getString("title"))); File tmp = new File(HttpTools.downloadPath + eps.getString("title") + ".mp4"); if (json.getBooleanValue("danmu") && !json.getBooleanValue("merge")) { List elems = buildDanmuHttp(json.getLong("cid"), json.getLong("avid"), 1); downDanmu("tmp", eps.getString("title"), elems); } if (tmp.exists()) { return; } JSONObject http = biliUtils.http("https://api.bilibili.com/x/player/playurl?" + HttpTools.toUrlParams(json), BiliBiliUtils.HTTP.GET, null, BiliBiliUtils.RET_MODEL.JSON); if (http.getInteger("code") == 0) { JSONObject data = http.getJSONObject("data"); JSONObject dash = data.getJSONObject("dash"); JSONObject video = dash.getJSONArray("video").getJSONObject(0); JSONObject audio = dash.getJSONArray("audio").getJSONObject(0); File videoFile = biliUtils.download(video.getString("baseUrl"), eps.getString("title") + "_video.mp4", false); if (videoFile == null) { downVideo(json, eps); return; } File audioFile = biliUtils.download(audio.getString("baseUrl"), eps.getString("title") + "_audio.mp3", false); if (audioFile == null) { videoFile.delete(); downVideo(json, eps); return; } save(eps.getString("title"), videoFile, audioFile); } } private void downDanmu(String savePath, String title, List danmuList) { try { AssTools tools = new AssTools(title); List list = new ArrayList<>(); for (VideoDanMu.DanmakuElem elem : danmuList) { DanmuData danmuData = new DanmuData(); danmuData.setDanmu(elem.getContent()); danmuData.setFontSize(elem.getFontsize()); danmuData.setTime(elem.getProgress()); danmuData.setFontColor(elem.getColor()); danmuData.setModel(elem.getMode()); list.add(danmuData); } tools.addDanmu(list); boolean saveDanmu = tools.saveDanmu(savePath); System.out.println("弹幕保存:" + saveDanmu); } catch (Exception e) { e.printStackTrace(); } } private List buildDanmuHttp(long cid, long avid, int segment_index) { List tmp, danmuList = new ArrayList<>(); JSONObject json = new JSONObject(); json.put("type", 1); json.put("oid", cid); json.put("avid", avid); json.put("segment_index", segment_index); while (!(tmp = getDanmu(json)).isEmpty()) { danmuList.addAll(tmp); json.put("segment_index", ++segment_index); } return danmuList; } private List getDanmu(JSONObject json) { try { byte[] http = biliUtils.http("https://api.bilibili.com/x/v2/dm/web/seg.so?" + HttpTools.toUrlParams(json), BiliBiliUtils.HTTP.GET, null, BiliBiliUtils.RET_MODEL.BYTE); VideoDanMu.DmSegMobileReply parse = VideoDanMu.DmSegMobileReply.parseFrom(http); return parse.getElemsList(); } catch (Exception e) { e.printStackTrace(); } return new ArrayList<>(); } private void save(String name, File videoFile, File audioFile) { List urls = new ArrayList<>(); urls.add(videoFile.getAbsolutePath()); urls.add(audioFile.getAbsolutePath()); StringBuilder builder = new StringBuilder(); builder.append(ConfigTools.load(ConfigTools.CONFIG, "ffmpeg", String.class)).append(" "); for (String _url : urls) { builder.append("-i").append(" "); builder.append("\"").append(_url).append("\"").append(" "); } builder.append("-vcodec").append(" "); builder.append("copy").append(" "); builder.append("-acodec").append(" "); builder.append("copy").append(" "); builder.append("-threads").append(" "); builder.append("8").append(" "); // builder.append("-y").append(" "); builder.append("\"").append(new File(HttpTools.downloadPath + name + ".mp4").getAbsolutePath()).append("\"").append(" "); System.out.println(builder); AppTools.exec(builder.toString(), new ObjectInterface() { @Override public void out(String data) { super.out(data); videoFile.delete(); audioFile.delete(); } }, false, false); } public JSONObject getVideoInfo(String url) { if (!new BiliLogin(qq).testLogin()) { System.err.println("未登录"); return null; } JSONObject json = buildJSON(url); if (json != null) { return biliUtils.http("https://api.bilibili.com/x/web-interface/view?" + HttpTools.toUrlParams(json), BiliBiliUtils.HTTP.GET, null, BiliBiliUtils.RET_MODEL.JSON); } return null; } private JSONObject buildJSON(String url) { if (!url.contains("?")) { url += "?"; } Pattern pattern = Pattern.compile("(?<=video/).*?(?=\\?)"); Matcher matcher = pattern.matcher(url); String id = null; if (matcher.find()) { id = matcher.group(); } if (id == null) { return null; } if (id.contains("/")) { id = id.replace("/", ""); } JSONObject json = new JSONObject(); if (id.startsWith("BV")) { json.put("bvid", id); } else { json.put("avid", id.toLowerCase().replace("av", "")); } return json; } public static void main(String[] args) { BiliVideo video = new BiliVideo(QQBotManager.defQQ); JSONObject login = new BiliLogin(QQBotManager.defQQ).login(); System.out.println(login); //岚少 480 //video.downVideo("https://www.bilibili.com/video/BV1Ps411m7pt?spm_id_from=333.999.0.0"); //唐诱 合集 //video.downVideo("https://www.bilibili.com/video/BV1Vv4y1K7ox?spm_id_from=444.41.top_right_bar_window_default_collection.content.click"); //邦邦 长视频 // video.downVideo("https://www.bilibili.com/video/BV1w5411271A?spm_id_from=444.41.list.card_archive.click"); //LK 超清4k hdr //video.downVideo("https://www.bilibili.com/video/BV1uZ4y1U7h8/?spm_id_from=333.788.recommend_more_video.-1"); // hdr // video.downVideo("https://www.bilibili.com/video/BV1rp4y1e745/?spm_id_from=333.788.recommend_more_video.-1"); // 1080+ 60fps //video.downVideo("https://www.bilibili.com/video/BV1qF411T7Vf?spm_id_from=444.41.list.card_archive.click"); //唐诱正片 //video.downVideo("https://www.bilibili.com/video/BV1L44y147zR?spm_id_from=333.999.0.0");// ep1 //video.downVideo("https://www.bilibili`.com/video/BV1Zu4y1B7DU/?spm_id_from=333.337.search-card.all.click", true, false);// ep5 // video.downVideo("https://www.bilibili.com/video/BV1SL411g7FS/?spm_id_from=333.788.recommend_more_video.0"); //all ig 1\5 // video.downVideo("https://www.bilibili.com/video/BV18L4y1H7rz?spm_id_from=333.999.0.0"); // video.downVideo("https://www.bilibili.com/video/BV1Pe4y1Q7MX?spm_id_from=444.41.top_right_bar_window_history.content.click"); // int a=16|2048; // System.out.println("a = " + a); //video.downDanmu(428855000L,976216102L,"【都市_情感】《唐可可的诱惑》第一集",1); video.downVideo("https://www.bilibili.com/bangumi/play/ep776259", false, false);// ep5 System.out.println("事件结束"); } }