baogutang-music/src/main/java/top/baogutang/music/schedule/QQDailyMusicHandler.java
2024-12-23 15:06:39 +08:00

200 lines
7.3 KiB
Java

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<DailySong> 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<byte[]> 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<DailySong> 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<String> 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> cdList;
}
@Data
public static class CdList implements Serializable {
private static final long serialVersionUID = -6612510307776519521L;
@JsonProperty("songids")
private String songIdList;
@JsonProperty("songlist")
private List<Song> 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;
}
}