package top.baogutang.music.schedule; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import top.baogutang.music.domain.res.DailySong; import top.baogutang.music.enums.ChannelEnum; import top.baogutang.music.exceptions.BusinessException; import top.baogutang.music.properties.QQMusicProperties; import top.baogutang.music.utils.JacksonUtil; import top.baogutang.music.utils.OkHttpUtil; import javax.annotation.Resource; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.Serializable; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.zip.GZIPInputStream; import java.util.zip.InflaterInputStream; /** * * @description: * * @author: N1KO * @date: 2024/12/23 : 09:51 */ @Slf4j @Component public class QQDailyMusicHandler extends DailyMusicHandler { @Resource private QQMusicProperties qqMusicProperties; @Override List fetchDaily() { HttpHeaders headers = new HttpHeaders(); headers.add(HttpHeaders.COOKIE, "ownCookie=" + qqMusicProperties.getCookie()); headers.add(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"); headers.add(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate"); ResponseEntity response = OkHttpUtil.get(qqMusicProperties.getDailyMusicBaseUrl(), headers, byte[].class); byte[] pageBytes = response.getBody(); HttpHeaders responseHeaders = response.getHeaders(); String contentEncoding = responseHeaders.getFirst(HttpHeaders.CONTENT_ENCODING); String pageContent = null; try { pageContent = decompressResponse(pageBytes, contentEncoding); } catch (IOException e) { log.error(">>>>>>>>>>decompress response error:{}<<<<<<<<<<", e.getMessage(), e); throw new BusinessException(e.getMessage()); } if (StringUtils.isBlank(pageContent)) { log.error(">>>>>>>>>>query qq daily music content is empty<<<<<<<<<<"); return Collections.emptyList(); } // 使用 Jsoup 解析 HTML Document doc = Jsoup.parse(pageContent); Element firstList = doc.selectFirst(".mod_for_u .playlist__item"); String id = ""; if (firstList != null) { Element nameElement = firstList.selectFirst(".playlist__name"); if (nameElement != null && "今日私享".equals(nameElement.text())) { Element linkElement = firstList.selectFirst(".playlist__link"); if (linkElement != null) { id = linkElement.attr("data-rid"); } } } return parseFromId(id); } @Override ChannelEnum getChannel() { return ChannelEnum.QQ_MUSIC; } private List parseFromId(String id) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.add(HttpHeaders.REFERER, "https://y.qq.com/n/yqq/playlist"); headers.add(HttpHeaders.COOKIE, "ownCookie=" + qqMusicProperties.getCookie()); // 构建请求体 ResponseEntity response = OkHttpUtil.get(String.format(qqMusicProperties.getDailyMusicSearchUrl(), id), headers, String.class); String body = response.getBody(); if (StringUtils.isBlank(body)) { return null; } String bodyJson = extractJson(body, "jsonCallback"); QQDailyCommonRes qqDailyCommonRes = JacksonUtil.fromJson(bodyJson, QQDailyCommonRes.class); if (Objects.isNull(qqDailyCommonRes) || Objects.isNull(qqDailyCommonRes.getCdList()) || CollectionUtils.isEmpty(qqDailyCommonRes.getCdList())) { return null; } return qqDailyCommonRes.getCdList().stream() .map(cdList -> cdList.getSongList().stream() .map(s -> { DailySong dailySong = new DailySong(); dailySong.setAlbumId(s.getAlbumId()); dailySong.setAlbumMid(s.getAlbumMid()); dailySong.setAlbumName(s.getAlbumName()); dailySong.setSongId(s.getSongId()); dailySong.setSongMid(s.getSongMid()); dailySong.setSongName(s.getSongName()); return dailySong; }) .collect(Collectors.toList())) .flatMap(Collection::stream) .collect(Collectors.toList()); } private static String extractJson(String jsonp, String callback) { String regex = "^" + Pattern.quote(callback) + "\\((.*)\\)$"; Pattern pattern = Pattern.compile(regex, Pattern.DOTALL); Matcher matcher = pattern.matcher(jsonp); if (matcher.find()) { return matcher.group(1); } return null; } private String decompressResponse(byte[] data, String contentEncoding) throws IOException { if ("gzip".equalsIgnoreCase(contentEncoding)) { try (GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(data))) { return new String(gis.readAllBytes(), StandardCharsets.UTF_8); } } else if ("deflate".equalsIgnoreCase(contentEncoding)) { try (InflaterInputStream iis = new InflaterInputStream(new ByteArrayInputStream(data))) { return new String(iis.readAllBytes(), StandardCharsets.UTF_8); } } else { // 未压缩或其他编码类型 return new String(data, StandardCharsets.UTF_8); } } @Data public static class QQDailyCommonRes implements Serializable { private static final long serialVersionUID = 1221533727687642619L; private Integer code; @JsonProperty("subcode") private Integer subCode; @JsonProperty("cdlist") private List cdList; } @Data public static class CdList implements Serializable { private static final long serialVersionUID = -6612510307776519521L; @JsonProperty("songids") private String songIdList; @JsonProperty("songlist") private List songList; } @Data public static class Song implements Serializable { private static final long serialVersionUID = -6515525575728411341L; @JsonProperty("albumid") private Long albumId; @JsonProperty("albummid") private String albumMid; @JsonProperty("albumname") private String albumName; @JsonProperty("songid") private Long songId; @JsonProperty("songmid") private String songMid; @JsonProperty("songname") private String songName; } }