200 lines
7.3 KiB
Java
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;
|
|
}
|
|
|
|
}
|