add 酷狗音乐, 酷我音乐, 网易云音乐, QQ音乐

This commit is contained in:
QAIU
2024-10-25 14:38:57 +08:00
parent cd4b208be9
commit adf56cd768
18 changed files with 376 additions and 112 deletions

View File

@@ -211,7 +211,7 @@ public class ReverseProxyVerticle extends AbstractVerticle {
port = 80; port = 80;
} }
String originPath = url.getPath(); String originPath = url.getPath();
LOGGER.info("Conf(path, originPath, host, port) ----> {},{},{},{}", path, originPath, host, port); LOGGER.info("path {}, originPath {}, to {}:{}", path, originPath, host, port);
// 注意这里不能origin多个代理地址, 一个实例只能代理一个origin // 注意这里不能origin多个代理地址, 一个实例只能代理一个origin
final HttpProxy httpProxy = HttpProxy.reverseProxy(httpClient); final HttpProxy httpProxy = HttpProxy.reverseProxy(httpClient);

View File

@@ -10,6 +10,11 @@
</parent> </parent>
<artifactId>parser</artifactId> <artifactId>parser</artifactId>
<packaging>jar</packaging>
<name>${project.groupId}:${project.artifactId}</name>
<description>NFD parser</description>
<url>https://qaiu.top</url>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>
@@ -46,10 +51,39 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.13.2</version> <version>${junit.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<licenses>
<license>
<name>MIT License</name>
<url>https://opensource.org/license/mit</url>
</license>
</licenses>
<developers>
<developer>
<name>qaiu</name>
<email>qaiu00@gmail.com</email>
<organization>https://qaiu.top</organization>
</developer>
</developers>
<scm>
<connection>scm:git@github.com:qaiu/netdisk-fast-download.git</connection>
<developerConnection>scm:git@github.com:qaiu/netdisk-fast-download.git</developerConnection>
<url>git@github.com:qaiu/netdisk-fast-download.git</url>
</scm>
<distributionManagement>
<snapshotRepository>
<id>sonatype</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
</snapshotRepository>
<repository>
<id>sonatype</id>
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<build> <build>
<plugins> <plugins>
@@ -62,6 +96,49 @@
<target>${java.version}</target> <target>${java.version}</target>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<attach>true</attach>
</configuration>
<executions>
<execution>
<id>attach-sources</id>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<!-- Gpg Signature -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.6.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
<autoPublish>true</autoPublish>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@@ -2,6 +2,7 @@ package cn.qaiu.parser;
import cn.qaiu.WebClientVertxInit; import cn.qaiu.WebClientVertxInit;
import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.entity.ShareLinkInfo;
import io.vertx.core.Future;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.core.Promise; import io.vertx.core.Promise;
import io.vertx.core.json.DecodeException; import io.vertx.core.json.DecodeException;
@@ -13,11 +14,14 @@ import io.vertx.ext.web.client.WebClientSession;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.Iterator;
/** /**
* 解析器抽象类包含promise, HTTP Client, 默认失败方法等; * 解析器抽象类包含promise, HTTP Client, 默认失败方法等;
* 新增网盘解析器需要继承该类. <br> * 新增网盘解析器需要继承该类. <br>
* <h2>实现类命名规则: </h2> * <h2>实现类命名规则: </h2>
* <p>{网盘标识}Tool, 网盘标识不超过3个字符, 可以取网盘名称首字母缩写或拼音首字母, <br> * <p>{网盘标识}Tool, 网盘标识不超过5个字符, 可以取网盘名称首字母缩写或拼音首字母, <br>
* 音乐类型的解析以M开头, 例如网易云音乐Mne</p> * 音乐类型的解析以M开头, 例如网易云音乐Mne</p>
*/ */
public abstract class PanBase implements IPanTool { public abstract class PanBase implements IPanTool {
@@ -73,11 +77,11 @@ public abstract class PanBase implements IPanTool {
try { try {
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args); String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
log.error("解析异常: " + s, t.fillInStackTrace()); log.error("解析异常: " + s, t.fillInStackTrace());
promise.fail(this.getClass().getSimpleName() + ": 解析异常: " + s + " -> " + t); promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": 解析异常: " + s + " -> " + t);
} catch (Exception e) { } catch (Exception e) {
log.error("ErrorMsg format fail. The parameter has been discarded", e); log.error("ErrorMsg format fail. The parameter has been discarded", e);
log.error("解析异常: " + errorMsg, t.fillInStackTrace()); log.error("解析异常: " + errorMsg, t.fillInStackTrace());
promise.fail(this.getClass().getSimpleName() + ": 解析异常: " + errorMsg + " -> " + t); promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + ": 解析异常: " + errorMsg + " -> " + t);
} }
} }
@@ -91,11 +95,11 @@ public abstract class PanBase implements IPanTool {
try { try {
String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args); String s = String.format(errorMsg.replaceAll("\\{}", "%s"), args);
log.error("解析异常: " + s); log.error("解析异常: " + s);
promise.fail(this.getClass().getSimpleName() + " - 解析异常: " + s); promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 解析异常: " + s);
} catch (Exception e) { } catch (Exception e) {
log.error("ErrorMsg format fail. The parameter has been discarded", e); log.error("ErrorMsg format fail. The parameter has been discarded", e);
log.error("解析异常: " + errorMsg); log.error("解析异常: " + errorMsg);
promise.fail(this.getClass().getSimpleName() + " - 解析异常: " + errorMsg); promise.fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 解析异常: " + errorMsg);
} }
} }
@@ -106,7 +110,7 @@ public abstract class PanBase implements IPanTool {
* @return Handler * @return Handler
*/ */
protected Handler<Throwable> handleFail(String errorMsg) { protected Handler<Throwable> handleFail(String errorMsg) {
return t -> fail(this.getClass().getSimpleName() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace()); return t -> fail(shareLinkInfo.getPanName() + "-" + shareLinkInfo.getType() + " - 请求异常 {}: -> {}", errorMsg, t.fillInStackTrace());
} }
@@ -128,4 +132,30 @@ public abstract class PanBase implements IPanTool {
promise.complete(url); promise.complete(url);
} }
protected Future<String> future() {
return promise.future();
}
/**
* 调用下一个解析器, 通用域名解析
*/
protected void nextParser() {
Iterator<PanDomainTemplate> iterator = Arrays.asList(PanDomainTemplate.values()).iterator();
while (iterator.hasNext()) {
if (iterator.next().name().equalsIgnoreCase(shareLinkInfo.getType())) {
if (iterator.hasNext()) {
PanDomainTemplate next = iterator.next();
log.debug("规则不匹配, 处理解析器转发: {} -> {}", shareLinkInfo.getPanName(), next.getDisplayName());
ParserCreate.fromType(next.name())
.fromAnyShareUrl(shareLinkInfo.getShareUrl())
.createTool()
.parse()
.onComplete(promise);
} else {
fail("error: 没有下一个解析处理器");
}
}
}
}
} }

View File

@@ -4,8 +4,11 @@ import cn.qaiu.parser.impl.*;
import java.util.Arrays; import java.util.Arrays;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.util.regex.Pattern.compile;
/** /**
* 枚举类 PanDomainTemplate 定义了不同网盘服务的模板信息,包括: * 枚举类 PanDomainTemplate 定义了不同网盘服务的模板信息,包括:
* <ul> * <ul>
@@ -20,126 +23,144 @@ import java.util.stream.Collectors;
*/ */
public enum PanDomainTemplate { public enum PanDomainTemplate {
// 网盘定义 // 网盘定义
LZ("蓝奏云", LZ("蓝奏云",
"https://([a-z0-9-]+)?\\.?lanzou[a-z]\\.com/(.+/)?(.+)", compile("https://([a-z0-9-]+)?\\.?lanzou[a-z]\\.com/(.+/)?(?<KEY>.+)"),
"https://lanzoux.com/{shareKey}", "https://lanzoux.com/{shareKey}",
LzTool.class), LzTool.class),
// https://www.feijix.com/s/ // https://www.feijix.com/s/
// https://share.feijipan.com/s/ // https://share.feijipan.com/s/
FJ("小飞机网盘", FJ("小飞机网盘",
"https://(share\\.feijipan\\.com|www\\.feijix\\.com)/s/(.+)", compile("https://(share\\.feijipan\\.com|www\\.feijix\\.com)/s/(?<KEY>.+)"),
"https://www.feijix.com/s/{shareKey}", "https://www.feijix.com/s/{shareKey}",
FjTool.class), FjTool.class),
// https://lecloud.lenovo.com/share/ // https://lecloud.lenovo.com/share/
LE("联想乐云", LE("联想乐云",
"https://lecloud?\\.lenovo\\.com/share/(.+)", compile("https://lecloud?\\.lenovo\\.com/share/(?<KEY>.+)"),
"https://lecloud.lenovo.com/share/{shareKey}", "https://lecloud.lenovo.com/share/{shareKey}",
LeTool.class), LeTool.class),
// https://v2.fangcloud.com/s/ // https://v2.fangcloud.com/s/
FC("亿方云", FC("亿方云",
"https://v2\\.fangcloud\\.(com|cn)/(s|sharing)/([^/]+)", compile("https://v2\\.fangcloud\\.(com|cn)/(s|sharing)/(?<KEY>.+)"),
"https://v2.fangcloud.com/s/{shareKey}", "https://v2.fangcloud.com/s/{shareKey}",
FcTool.class), FcTool.class),
// https://www.ilanzou.com/s/ // https://www.ilanzou.com/s/
IZ("蓝奏云优享", IZ("蓝奏云优享",
"https://www\\.ilanzou\\.com/s/(.+)", compile("https://www\\.ilanzou\\.com/s/(?<KEY>.+)"),
"https://www.ilanzou.com/s/{shareKey}", "https://www.ilanzou.com/s/{shareKey}",
IzTool.class), IzTool.class),
// https://wx.mail.qq.com/ftn/download? // https://wx.mail.qq.com/ftn/download?
QQ("QQ邮箱中转站", QQ("QQ邮箱中转站",
"https://i?wx\\.mail\\.qq\\.com/ftn/download\\?(.+)", compile("https://i?wx\\.mail\\.qq\\.com/ftn/download\\?(?<KEY>.+)"),
"https://iwx.mail.qq.com/ftn/download/{shareKey}", "https://iwx.mail.qq.com/ftn/download/{shareKey}",
QQTool.class), QQTool.class),
// https://f.ws59.cn/f/或者https://www.wenshushu.cn/f/ // https://f.ws59.cn/f/或者https://www.wenshushu.cn/f/
WS("文叔叔", WS("文叔叔",
"https://(f\\.ws([0-9]{2})\\.cn|www\\.wenshushu\\.cn)/f/(.+)", compile("https://(f\\.ws([0-9]{2})\\.cn|www\\.wenshushu\\.cn)/f/(?<KEY>.+)"),
"https://www.wenshushu.cn/f/{shareKey}", "https://www.wenshushu.cn/f/{shareKey}",
WsTool.class), WsTool.class),
// https://www.123pan.com/s/ // https://www.123pan.com/s/
YE("123网盘", YE("123网盘",
"https://www\\.(123pan|123865|123684)\\.com/s/(.+)", compile("https://www\\.(123pan|123865|123684)\\.com/s/(?<KEY>.+)(.html)?"),
"https://www.123pan.com/s/{shareKey}", "https://www.123pan.com/s/{shareKey}",
YeTool.class), YeTool.class),
// https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={code}&isShare=1 // https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={code}&isShare=1
EC("移动云空间", EC("移动云空间",
"https://www\\.ecpan\\.cn/web(/%23|/#)?/yunpanProxy\\?path=.*&data=" + compile("https://www\\.ecpan\\.cn/web(/%23|/#)?/yunpanProxy\\?path=.*&data=" +
"([^&]+)&isShare=1", "(?<KEY>[^&]+)&isShare=1"),
"https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={shareKey}&isShare=1", "https://www.ecpan.cn/web/#/yunpanProxy?path=%2F%23%2Fdrive%2Foutside&data={shareKey}&isShare=1",
EcTool.class), EcTool.class),
// https://cowtransfer.com/s/ // https://cowtransfer.com/s/
COW("奶牛快传", COW("奶牛快传",
"https://(.*)cowtransfer\\.com/s/(.+)", compile("https://(.*)cowtransfer\\.com/s/(?<KEY>.+)"),
"https://cowtransfer.com/s/{shareKey}", "https://cowtransfer.com/s/{shareKey}",
CowTool.class), CowTool.class),
CT("城通网盘", CT("城通网盘",
"https://474b\\.com/file/(.+)", compile("https://474b\\.com/file/(?<KEY>.+)"),
"https://474b.com/file/{shareKey}", "https://474b.com/file/{shareKey}",
CtTool.class), CtTool.class),
// =====================音乐类解析 分享链接标志->MxxS (单歌曲/普通音质)========================== // =====================音乐类解析 分享链接标志->MxxS (单歌曲/普通音质)==========================
// http://163cn.tv/xxx // http://163cn.tv/xxx
MNES("网易云音乐分享", MNES("网易云音乐分享",
"http(s)?://163cn\\.tv/(.+)", compile("http(s)?://163cn\\.tv/(?<KEY>.+)"),
"http://163cn.tv/{shareKey}", "http://163cn.tv/{shareKey}",
MnesTool.class), MnesTool.class),
MNE("网易云音乐", // https://music.163.com/#/song?id=xxx
"https://music\\.163\\.com/(#/)?song\\?id=(.+)", MNE("网易云音乐歌曲详情",
compile("https://music\\.163\\.com/(#/)?song\\?id=(?<KEY>.+)"),
"https://music.163.com/#/song?id={shareKey}", "https://music.163.com/#/song?id={shareKey}",
MnesTool.MneTool.class), MnesTool.MneTool.class),
// https://c6.y.qq.com/base/fcgi-bin/u?__=xxx // https://c6.y.qq.com/base/fcgi-bin/u?__=xxx
MQQS("QQ音乐分享", MQQS("QQ音乐分享",
"https://(.+)\\.y\\.qq\\.com/base/fcgi-bin/u\\?__=(.+)", compile("https://(.+)\\.y\\.qq\\.com/base/fcgi-bin/u\\?__=(?<KEY>.+)"),
"https://c6.y.qq.com/base/fcgi-bin/u?__={shareKey}", "https://c6.y.qq.com/base/fcgi-bin/u?__={shareKey}",
MqqTool.class), MqqsTool.class),
// https://y.qq.com/n/ryqq/songDetail/000XjcLg0fbRjv?songtype=0 // https://y.qq.com/n/ryqq/songDetail/000XjcLg0fbRjv?songtype=0
MQQ("QQ音乐", MQQ("QQ音乐歌曲详情",
"https://y\\.qq\\.com/n/ryqq/songDetail/(.+)\\?.*", compile("https://y\\.qq\\.com/n/ryqq/songDetail/(?<KEY>.+)(\\?.*)?"),
"https://y.qq.com/n/ryqq/songDetail/{shareKey}", "https://y.qq.com/n/ryqq/songDetail/{shareKey}",
MqqTool.class), MqqsTool.MqqTool.class),
// https://t1.kugou.com/song.html?id=xxx // https://t1.kugou.com/song.html?id=xxx
MKGS("酷狗音乐分享", MKGS("酷狗音乐分享",
"https://(.+)\\.kugou\\.com/song\\.html\\?id=(.+)", compile("https://(.+)\\.kugou\\.com/song\\.html\\?id=(?<KEY>.+)"),
"https://t1.kugou.com/song.html?id={shareKey}", "https://t1.kugou.com/song.html?id={shareKey}",
MkgsTool.class), MkgsTool.class),
// https://www.kugou.com/share/2bi8Fe9CSV3.html?id=2bi8Fe9CSV3#6ed9gna4" // https://www.kugou.com/share/2bi8Fe9CSV3.html?id=2bi8Fe9CSV3#6ed9gna4"
MKGS2("酷狗音乐分享2", MKGS2("酷狗音乐分享2",
"https://www\\.kugou\\.com/share/(.+).html\\?.*", compile("https://(.+)\\.kugou\\.com/share/(?<KEY>.+).html.*"),
"https://www.kugou.com/share/{shareKey}.html", "https://www.kugou.com/share/{shareKey}.html",
MkgsTool.Mkgs2Tool.class), MkgsTool.Mkgs2Tool.class),
// https://www.kugou.com/mixsong/2bi8Fe9CSV3 // https://www.kugou.com/mixsong/2bi8Fe9CSV3
MKG("酷狗音乐", MKG("酷狗音乐歌曲详情",
"https://(.+)\\.kugou\\.com/song\\.html\\?id=(.+)", compile("https://(.+)\\.kugou\\.com/mixsong/(?<KEY>.+)\\.html.*"),
"https://www.kugou.com/mixsong/{shareKey}", "https://www.kugou.com/mixsong/{shareKey}.html",
MkgsTool.MkgTool.class), MkgsTool.MkgTool.class),
// // https://kuwo.cn/play_detail/395500809
MKWS("酷我音乐分享*", MKWS("酷我音乐分享*",
"https://(.+)\\.kugou\\.com/song\\.html\\?id=(.+)", compile("https://kuwo\\.cn/play_detail/(?<KEY>.+)"),
"https://t1.kugou.com/song.html?id={shareKey}", "https://kuwo.cn/play_detail/{shareKey}",
MkwTool.class), MkwTool.class),
// // https://music.migu.cn/v3/music/song/6326951FKBJ?channelId=001002H
MMGS("咪咕音乐分享", MMGS("咪咕音乐分享",
"https://(.+)\\.kugou\\.com/song\\.html\\?id=(.+)", compile("https://music\\.migu\\.cn/v3/music/song/(?<KEY>.+)(\\?.*)?"),
"https://t1.kugou.com/song.html?id={shareKey}", "https://music.migu.cn/v3/music/song/{shareKey}",
MkwTool.class), MkwTool.class),
// =====================私有盘解析========================== // =====================私有盘解析==========================
// Cloudreve自定义域名解析, 解析器CeTool兜底策略, 即任意域名如果匹配不到对应的规则, 则由CeTool统一处理,
// 如果不属于Cloudreve盘 则调用下一个自定义域名解析器, 若都处理不了则抛出异常, 这种匹配模式类似责任链
// https://pan.huang1111.cn/s/xxx // https://pan.huang1111.cn/s/xxx
// 通用域名([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,} // 通用域名([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}
CE("Cloudreve", CE("Cloudreve",
"https://([a-z0-9]+(-[a-z0-9]+)*\\.)+[a-z]{2,}/s/(.+)", compile("https://([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}(/s)?/(?<KEY>.+)"),
"https://{CloudreveDomainName}/s/{shareKey}", "https://{any}/s/{shareKey}",
CeTool.class); CeTool.class),
// 可道云自定义域名解析
KD("可道云",
compile("http(s)?://([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}(/#s)?/(?<KEY>.+)"),
"https://{any}/#s/{shareKey}",
KdTool.class),
// 其他自定义域名解析
OTHER("其他网盘",
compile("http(s)?://([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\\.)+[a-zA-Z]{2,}/(?<KEY>.+)"),
"https://{any}/{shareKey}",
OtherTool.class);
public static final String KEY = "KEY";
// 网盘的显示名称,用于用户界面显示 // 网盘的显示名称,用于用户界面显示
private final String displayName; private final String displayName;
// 用于匹配和解析分享链接的正则表达式保证最后一个捕捉组能匹配到分享key // 用于匹配和解析分享链接的正则表达式保证最后一个捕捉组能匹配到分享key
private final String regexPattern; private final Pattern pattern;
private final String regex;
// 网盘的标准链接模板,不含占位符,用于规范化分享链接 // 网盘的标准链接模板,不含占位符,用于规范化分享链接
private final String standardUrlTemplate; private final String standardUrlTemplate;
@@ -147,10 +168,11 @@ public enum PanDomainTemplate {
// 指向解析工具IPanTool实现类 // 指向解析工具IPanTool实现类
private final Class<? extends IPanTool> toolClass; private final Class<? extends IPanTool> toolClass;
PanDomainTemplate(String displayName, String regexPattern, String standardUrlTemplate, PanDomainTemplate(String displayName, Pattern pattern, String standardUrlTemplate,
Class<? extends IPanTool> toolClass) { Class<? extends IPanTool> toolClass) {
this.displayName = displayName; this.displayName = displayName;
this.regexPattern = regexPattern; this.pattern = pattern;
this.regex = pattern.pattern();
this.standardUrlTemplate = standardUrlTemplate; this.standardUrlTemplate = standardUrlTemplate;
this.toolClass = toolClass; this.toolClass = toolClass;
} }
@@ -159,8 +181,12 @@ public enum PanDomainTemplate {
return displayName; return displayName;
} }
public String getRegexPattern() { public Pattern getPattern() {
return regexPattern; return pattern;
}
public String getRegex() {
return regex;
} }
public String getStandardUrlTemplate() { public String getStandardUrlTemplate() {
@@ -174,7 +200,7 @@ public enum PanDomainTemplate {
public static void main(String[] args) { public static void main(String[] args) {
// 校验重复 // 校验重复
Set<String> collect = Set<String> collect =
Arrays.stream(PanDomainTemplate.values()).map(PanDomainTemplate::getRegexPattern).collect(Collectors.toSet()); Arrays.stream(PanDomainTemplate.values()).map(PanDomainTemplate::getRegex).collect(Collectors.toSet());
if (collect.size()<PanDomainTemplate.values().length) { if (collect.size()<PanDomainTemplate.values().length) {
System.out.println("有重复枚举正则"); System.out.println("有重复枚举正则");
} }

View File

@@ -1,13 +0,0 @@
package cn.qaiu.parser;
/**
* @author <a href="https://qaiu.top">QAIU</a>
* @date 2023/6/13 4:26
*/
public enum PanType {
LZ("lz"),
COW("cow");
PanType(String type) {
}
}

View File

@@ -4,7 +4,8 @@ import cn.qaiu.entity.ShareLinkInfo;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static cn.qaiu.parser.PanDomainTemplate.KEY;
/** /**
@@ -34,15 +35,14 @@ public class ParserCreate {
if (StringUtils.isEmpty(shareUrl)) { if (StringUtils.isEmpty(shareUrl)) {
throw new IllegalArgumentException("ShareLinkInfo shareUrl is empty"); throw new IllegalArgumentException("ShareLinkInfo shareUrl is empty");
} }
Pattern pattern = Pattern.compile(this.panDomainTemplate.getRegexPattern()); Matcher matcher = this.panDomainTemplate.getPattern().matcher(shareUrl);
Matcher matcher = pattern.matcher(shareUrl);
if (matcher.find()) { if (matcher.find()) {
String shareKey = matcher.group(matcher.groupCount()); String shareKey = matcher.group(KEY);
// 返回规范化的标准链接 // 返回规范化的标准链接
String standardUrl = getStandardUrlTemplate().replace("{shareKey}", shareKey); String standardUrl = getStandardUrlTemplate().replace("{shareKey}", shareKey);
shareLinkInfo.setShareUrl(shareUrl); shareLinkInfo.setShareUrl(shareUrl);
shareLinkInfo.setShareKey(shareKey); shareLinkInfo.setShareKey(shareKey);
if (!(panDomainTemplate == PanDomainTemplate.CE)) { if (!(panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal())) {
shareLinkInfo.setStandardUrl(standardUrl); shareLinkInfo.setStandardUrl(standardUrl);
} }
return this; return this;
@@ -68,7 +68,7 @@ public class ParserCreate {
// set share key // set share key
public ParserCreate shareKey(String shareKey) { public ParserCreate shareKey(String shareKey) {
if (panDomainTemplate == PanDomainTemplate.CE) { if (panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal()) {
// 处理Cloudreve(ce)类: pan.huang1111.cn_s_wDz5TK _ -> / // 处理Cloudreve(ce)类: pan.huang1111.cn_s_wDz5TK _ -> /
String[] s = shareKey.split("_"); String[] s = shareKey.split("_");
String standardUrl = "https://" + String.join("/", s); String standardUrl = "https://" + String.join("/", s);
@@ -82,6 +82,13 @@ public class ParserCreate {
return this; return this;
} }
// set any share url
public ParserCreate fromAnyShareUrl(String url) {
shareLinkInfo.setStandardUrl(url);
shareLinkInfo.setShareUrl(url);
return this;
}
public String getStandardUrlTemplate() { public String getStandardUrlTemplate() {
return this.panDomainTemplate.getStandardUrlTemplate(); return this.panDomainTemplate.getStandardUrlTemplate();
} }
@@ -98,12 +105,12 @@ public class ParserCreate {
// 根据分享链接获取PanDomainTemplate实例 // 根据分享链接获取PanDomainTemplate实例
public synchronized static ParserCreate fromShareUrl(String shareUrl) { public synchronized static ParserCreate fromShareUrl(String shareUrl) {
for (PanDomainTemplate panDomainTemplate : PanDomainTemplate.values()) { for (PanDomainTemplate panDomainTemplate : PanDomainTemplate.values()) {
if (shareUrl.matches(panDomainTemplate.getRegexPattern())) { if (panDomainTemplate.getPattern().matcher(shareUrl).matches()) {
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder() ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
.type(panDomainTemplate.name().toLowerCase()) .type(panDomainTemplate.name().toLowerCase())
.panName(panDomainTemplate.getDisplayName()) .panName(panDomainTemplate.getDisplayName())
.shareUrl(shareUrl).build(); .shareUrl(shareUrl).build();
if (panDomainTemplate == PanDomainTemplate.CE) { if (panDomainTemplate.ordinal() >= PanDomainTemplate.CE.ordinal()) {
shareLinkInfo.setStandardUrl(shareUrl); shareLinkInfo.setStandardUrl(shareUrl);
} }
ParserCreate parserCreate = new ParserCreate(panDomainTemplate, shareLinkInfo); ParserCreate parserCreate = new ParserCreate(panDomainTemplate, shareLinkInfo);
@@ -119,6 +126,7 @@ public class ParserCreate {
PanDomainTemplate panDomainTemplate = Enum.valueOf(PanDomainTemplate.class, type.toUpperCase()); PanDomainTemplate panDomainTemplate = Enum.valueOf(PanDomainTemplate.class, type.toUpperCase());
ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder() ShareLinkInfo shareLinkInfo = ShareLinkInfo.newBuilder()
.type(type.toLowerCase()).build(); .type(type.toLowerCase()).build();
shareLinkInfo.setPanName(panDomainTemplate.getDisplayName());
return new ParserCreate(panDomainTemplate, shareLinkInfo); return new ParserCreate(panDomainTemplate, shareLinkInfo);
} catch (IllegalArgumentException ignore) { } catch (IllegalArgumentException ignore) {
// 如果没有找到对应的枚举实例,抛出异常 // 如果没有找到对应的枚举实例,抛出异常

View File

@@ -2,12 +2,16 @@ package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase; import cn.qaiu.parser.PanBase;
import cn.qaiu.parser.PanDomainTemplate;
import cn.qaiu.parser.ParserCreate;
import io.vertx.core.Future; import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer; import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject; import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpRequest; import io.vertx.ext.web.client.HttpRequest;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
import java.util.Iterator;
/** /**
* <a href="https://github.com/cloudreve/Cloudreve">Cloudreve自建网盘解析</a> <br> * <a href="https://github.com/cloudreve/Cloudreve">Cloudreve自建网盘解析</a> <br>
@@ -42,22 +46,32 @@ public class CeTool extends PanBase {
String downloadApiUrl = url.getProtocol() + "://" + url.getHost() + DOWNLOAD_API_PATH + key + "?path" + String downloadApiUrl = url.getProtocol() + "://" + url.getHost() + DOWNLOAD_API_PATH + key + "?path" +
"=undefined/undefined;"; "=undefined/undefined;";
String shareApiUrl = url.getProtocol() + "://" + url.getHost() + SHARE_API_PATH + key; String shareApiUrl = url.getProtocol() + "://" + url.getHost() + SHARE_API_PATH + key;
// 设置cookie // 设置cookie
HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl); HttpRequest<Buffer> httpRequest = clientSession.getAbs(shareApiUrl);
if (pwd != null) { if (pwd != null) {
httpRequest.addQueryParam("password", pwd); httpRequest.addQueryParam("password", pwd);
} }
// 获取下载链接 // 获取下载链接
httpRequest.send().onSuccess(res -> getDownURL(downloadApiUrl)).onFailure(handleFail(shareApiUrl)); httpRequest.send().onSuccess(res -> {
try {
if (res.statusCode() == 200 && res.bodyAsJsonObject().containsKey("code")) {
getDownURL(downloadApiUrl);
} else {
nextParser();
}
} catch (Exception e) {
nextParser();
}
}).onFailure(handleFail(shareApiUrl));
} catch (Exception e) { } catch (Exception e) {
fail(e, "URL解析错误"); fail(e, "URL解析错误");
} }
return promise.future(); return promise.future();
} }
private void getDownURL(String apiUrl) {
clientSession.putAbs(apiUrl).send().onSuccess(res -> { private void getDownURL(String shareApiUrl) {
clientSession.putAbs(shareApiUrl).send().onSuccess(res -> {
JsonObject jsonObject = asJson(res); JsonObject jsonObject = asJson(res);
System.out.println(jsonObject.encodePrettily()); System.out.println(jsonObject.encodePrettily());
if (jsonObject.containsKey("code") && jsonObject.getInteger("code") == 0) { if (jsonObject.containsKey("code") && jsonObject.getInteger("code") == 0) {
@@ -65,6 +79,6 @@ public class CeTool extends PanBase {
} else { } else {
fail("JSON解析失败: {}", jsonObject.encodePrettily()); fail("JSON解析失败: {}", jsonObject.encodePrettily());
} }
}).onFailure(handleFail(apiUrl)); }).onFailure(handleFail(shareApiUrl));
} }
} }

View File

@@ -0,0 +1,26 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import java.util.UUID;
/**
* <a href="https://kodcloud.com/">可道云</a>
*/
public class KdTool extends PanBase {
private static final String API_URL_PREFIX = "";
public KdTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
nextParser();
// TODO
return future();
}
}

View File

@@ -3,7 +3,6 @@ package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo; import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase; import cn.qaiu.parser.PanBase;
import io.vertx.core.Future; import io.vertx.core.Future;
import io.vertx.uritemplate.UriTemplate;
/** /**
* 咪咕音乐分享 * 咪咕音乐分享
@@ -20,6 +19,8 @@ public class MmgTool extends PanBase {
public Future<String> parse() { public Future<String> parse() {
String shareUrl = shareLinkInfo.getStandardUrl(); String shareUrl = shareLinkInfo.getStandardUrl();
// TODO
promise.complete("暂未实现, 敬请期待");
return promise.future(); return promise.future();
} }
} }

View File

@@ -34,7 +34,12 @@ public class MnesTool extends PanBase {
String id = URLUtil.from(locationURL).getParam("id"); String id = URLUtil.from(locationURL).getParam("id");
clientNoRedirects.getAbs(UriTemplate.of(API_URL)).setTemplateParam("id", id).send() clientNoRedirects.getAbs(UriTemplate.of(API_URL)).setTemplateParam("id", id).send()
.onSuccess(res2 -> { .onSuccess(res2 -> {
promise.complete(res2.headers().get("Location")); String location = res2.headers().get("Location");
if (location.endsWith("/404")) {
fail("链接已失效: id={}", id);
} else {
promise.complete(location);
}
}).onFailure(handleFail(API_URL.replace("{id}", id))); }).onFailure(handleFail(API_URL.replace("{id}", id)));
} }

View File

@@ -14,7 +14,7 @@ import io.vertx.uritemplate.UriTemplate;
* <a href="https://c6.y.qq.com/base/fcgi-bin/u?__=w3lqEpOHACLO">分享示例</a> * <a href="https://c6.y.qq.com/base/fcgi-bin/u?__=w3lqEpOHACLO">分享示例</a>
* <a href="https://y.qq.com/n/ryqq/songDetail/000XjcLg0fbRjv?songtype=0">详情页</a> * <a href="https://y.qq.com/n/ryqq/songDetail/000XjcLg0fbRjv?songtype=0">详情页</a>
*/ */
public class MqqTool extends PanBase { public class MqqsTool extends PanBase {
public static final String API_URL = "https://u.y.qq.com/cgi-bin/musicu" + public static final String API_URL = "https://u.y.qq.com/cgi-bin/musicu" +
".fcg?-=getplaysongvkey2682247447678878&g_tk=5381&loginUin=956581739&hostUin=0&format=json&inCharset=utf8" + ".fcg?-=getplaysongvkey2682247447678878&g_tk=5381&loginUin=956581739&hostUin=0&format=json&inCharset=utf8" +
@@ -24,7 +24,7 @@ public class MqqTool extends PanBase {
"%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A956581739%2C" + "%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A956581739%2C" +
"%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D"; "%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D";
public MqqTool(ShareLinkInfo shareLinkInfo) { public MqqsTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo); super(shareLinkInfo);
} }
@@ -36,9 +36,16 @@ public class MqqTool extends PanBase {
clientNoRedirects.getAbs(shareUrl).send().onSuccess(res -> { clientNoRedirects.getAbs(shareUrl).send().onSuccess(res -> {
String locationURL = res.headers().get("Location"); String locationURL = res.headers().get("Location");
String id = URLUtil.from(locationURL).getParam("songmid"); String id = URLUtil.from(locationURL).getParam("songmid");
downUrl(id);
}).onFailure(handleFail(shareUrl));
return promise.future();
}
protected void downUrl(String id) {
clientNoRedirects.getAbs(UriTemplate.of(API_URL)).setTemplateParam("songmid", id).send().onSuccess(res2 -> { clientNoRedirects.getAbs(UriTemplate.of(API_URL)).setTemplateParam("songmid", id).send().onSuccess(res2 -> {
JsonObject jsonObject = asJson(res2); JsonObject jsonObject = asJson(res2);
System.out.println(jsonObject.encodePrettily()); log.debug(jsonObject.encodePrettily());
try { try {
JsonObject data = jsonObject.getJsonObject("req_0").getJsonObject("data"); JsonObject data = jsonObject.getJsonObject("req_0").getJsonObject("data");
String path = data.getJsonArray("midurlinfo").getJsonObject(0).getString("purl"); String path = data.getJsonArray("midurlinfo").getJsonObject(0).getString("purl");
@@ -48,18 +55,24 @@ public class MqqTool extends PanBase {
} }
String downURL = data.getJsonArray("sip").getString(0) String downURL = data.getJsonArray("sip").getString(0)
.replace("http://", "https://") + path; .replace("http://", "https://") + path;
System.out.println(downURL);
promise.complete(downURL); promise.complete(downURL);
} catch (Exception e) { } catch (Exception e) {
fail("获取失败"); fail("获取失败");
} }
}).onFailure(handleFail(API_URL.replace("{id}", id))); }).onFailure(handleFail(API_URL.replace("{id}", id)));
}).onFailure(handleFail(shareUrl)); }
public static class MqqTool extends MqqsTool{
public MqqTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
@Override
public Future<String> parse() {
downUrl(shareLinkInfo.getShareKey());
return promise.future(); return promise.future();
} }
public static void main(String[] args) {
new MqqTool(ShareLinkInfo.newBuilder().build()).parse();
} }
} }

View File

@@ -0,0 +1,22 @@
package cn.qaiu.parser.impl;
import cn.qaiu.entity.ShareLinkInfo;
import cn.qaiu.parser.PanBase;
import io.vertx.core.Future;
/**
* 其他网盘解析
*/
public class OtherTool extends PanBase {
private static final String API_URL_PREFIX = "";
public OtherTool(ShareLinkInfo shareLinkInfo) {
super(shareLinkInfo);
}
public Future<String> parse() {
// TODO
fail("暂未实现, 敬请期待");
return future();
}
}

View File

@@ -1,25 +1,32 @@
package cn.qaiu.util; package cn.qaiu.util;
import org.apache.commons.lang3.StringUtils;
import java.net.URL; import java.net.URL;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class URLUtil { public class URLUtil {
private Map<String, String> queryParams = new HashMap<>(); private final Map<String, String> queryParams = new HashMap<>();
// 构造函数传入URL并解析参数 // 构造函数传入URL并解析参数
private URLUtil(String url) { private URLUtil(String url) {
try { try {
URL parsedUrl = new URL(url); URL parsedUrl = new URL(url);
String ref = parsedUrl.getRef();
if (StringUtils.isNotEmpty(ref)) {
parsedUrl = new URL(parsedUrl.getProtocol() + "://" + parsedUrl.getHost() + ref);
}
String query = parsedUrl.getQuery(); String query = parsedUrl.getQuery();
if (query != null) { if (query != null) {
String[] pairs = query.split("&"); String[] pairs = query.split("&");
for (String pair : pairs) { for (String pair : pairs) {
String[] keyValue = pair.split("="); String[] keyValue = pair.split("=");
String key = URLDecoder.decode(keyValue[0], "UTF-8"); String key = URLDecoder.decode(keyValue[0], StandardCharsets.UTF_8);
String value = keyValue.length > 1 ? URLDecoder.decode(keyValue[1], "UTF-8") : ""; String value = keyValue.length > 1 ? URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8) : "";
queryParams.put(key, value); queryParams.put(key, value);
} }
} }

View File

@@ -10,11 +10,10 @@ public class TestRegex {
@Test @Test
public void regexYFC() { public void regexYFC() {
String html = """ String html = """
<input type="hidden" id="typed_id" value="file_559003251828"> https://www.kugou.com/mixsong/9q98o5b9.html
<input type="hidden" id="share_link_token" value="9cbe4b73521ba4d65a8cd38a8c">
"""; """;
Pattern compile = Pattern.compile("id=\"typed_id\"\\s+value=\"file_(\\d+)\""); Pattern compile = Pattern.compile("https://(.+)\\.kugou\\.com/mixsong/(?<KEY>.+).html");
Matcher matcher = compile.matcher(html); Matcher matcher = compile.matcher(html);
if (matcher.find()) { if (matcher.find()) {
System.out.println(matcher.group(0)); System.out.println(matcher.group(0));

View File

@@ -28,10 +28,6 @@ public class URLParamUtil {
if (params.contains("url")) { if (params.contains("url")) {
String encodedUrl = params.get("url"); String encodedUrl = params.get("url");
url = handleTruncatedUrl(encodedUrl, params); url = handleTruncatedUrl(encodedUrl, params);
if (url.endsWith(".html")) {
// 123云盘的后缀处理
url = url.replace(".html", "");
}
} }
return url; return url;
} }

View File

@@ -8,7 +8,10 @@ https://music.163.com/song?id=233334
### ###
#@no-redirect #@no-redirect
https://music.163.com/song/media/outer/url?id=233334 https://music.163.com/song/media/outer/url?id=2020593612
###
https://music.163.com/#/song?id=092087
### ###
@@ -166,3 +169,13 @@ https://ws6.stream.qqmusic.qq.com/C400003mAan70zUy5O.m4a?guid=2796982635&vkey=B2
### ###
https://ws.stream.qqmusic.qq.com/C400003mAan70zUy5O.m4a?fromtag=666&guid=2796982635&vkey=B2EDD08E318F0C0D2B3A9462FC5754CBCA9AEFD796FA0662C83A102821425D31547F957451751901F195095830842E1565FF8815B210B25A https://ws.stream.qqmusic.qq.com/C400003mAan70zUy5O.m4a?fromtag=666&guid=2796982635&vkey=B2EDD08E318F0C0D2B3A9462FC5754CBCA9AEFD796FA0662C83A102821425D31547F957451751901F195095830842E1565FF8815B210B25A
###
https://y.qq.com/n/ryqq/songDetail/000KKjzb2Eq15b
###
# @no-redirect
https://c6.y.qq.com/base/fcgi-bin/u?__=k8gafY6HAQ5Y
###
https://u.y.qq.com/cgi-bin/musicu.fcg?-=getplaysongvkey2682247447678878&g_tk=5381&loginUin=956581739&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=0&data=%7B%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%222796982635%22%2C%22songmid%22%3A%5B%22000KKjzb2Eq15b%22%5D%2C%22songtype%22%3A%5B1%5D%2C%22uin%22%3A%22956581739%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A956581739%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D

View File

@@ -64,3 +64,15 @@ sec-fetch-user: ?1
upgrade-insecure-requests: 1 upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded Content-Type: application/x-www-form-urlencoded
###
GET https://blog.qaiu.top/api/v3/share/download/nLNsQ
###
# @no-redirect
GET http://127.0.0.1:6401/parser?url=https://blog.qaiu.top/s/nLNsQ1
###
# @no-redirect
GET http://127.0.0.1:6401/parser?url=https://pan.seeoss.com/s/nLNsQ1

View File

@@ -153,8 +153,14 @@ GET http://127.0.0.1:6400/json/ce/pan.huang1111.cn_s_g31PcQ@qaiu
#GET http://127.0.0.1:6400/parser?url=https://pan.huang1111.cn/s/g31PcQ&pwd=qaiu #GET http://127.0.0.1:6400/parser?url=https://pan.huang1111.cn/s/g31PcQ&pwd=qaiu
# @no-redirect # @no-redirect
GET http://127.0.0.1:6400/parser?url=https://pan.seeoss.com/s/nLNsQ GET http://127.0.0.1:6401/parser?url=https://pan.seeoss.com/s/nLNsQ
###
# @no-redirect
GET http://127.0.0.1:6401/parser?url=https://blog.qaiu.top/s/nLNsQ
###
PUT https://blog.qaiu.top/s/nLNsQ
### PASS QQ ### PASS QQ
@@ -197,15 +203,37 @@ GET http://127.0.0.1:6401/parser?url=https://474b.com/file/4015376-131945810
### PASS MNE ### PASS MNE
# @no-redirect # @no-redirect
GET http://127.0.0.1:6401/parser?url=http://163cn.tv/ykLZJJT GET http://127.0.0.1:6401/json/parser?url=http://163cn.tv/ykLZJJT
### PASS MNE2
# @no-redirect
GET http://127.0.0.1:6401/parser?url=https://music.163.com/#/song?id=472194327
### PASS MQQ ### PASS MQQ
# @no-redirect # @no-redirect
GET http://127.0.0.1:6401/parser?url=https://c6.y.qq.com/base/fcgi-bin/u?__=k8gafY6HAQ5Y GET http://127.0.0.1:6401/parser?url=https://c6.y.qq.com/base/fcgi-bin/u?__=k8gafY6HAQ5Y
### PASS MQQ2
# @no-redirect
GET http://127.0.0.1:6401/parser?url=https://y.qq.com/n/ryqq/songDetail/000KKjzb2Eq15b
### PASS MKG0 酷狗
# @no-redirect
GET http://127.0.0.1:6401/parser?url=https://www.kugou.com/share/2bi8Fe9CSV3.html
### PASS MKG ### PASS MKG
# @no-redirect # @no-redirect
GET http://127.0.0.1:6401/json/parser?url=https://www.kugou.com/share/2bi8Fe9CSV3.html?id=2bi8Fe9CSV3#6ed9gna4
### PASS MKG2
# @no-redirect
GET http://127.0.0.1:6401/parser?url=https://t1.kugou.com/song.html?id=2bi8Fe9CSV3 GET http://127.0.0.1:6401/parser?url=https://t1.kugou.com/song.html?id=2bi8Fe9CSV3
### PASS MKG3
# @no-redirect
GET http://127.0.0.1:6401/parser?url=https://www.kugou.com/mixsong/9q98o5b9.html
### PASS MQW 酷我
# @no-redirect
GET http://127.0.0.1:6401/parser?url=https://kuwo.cn/play_detail/395500809
### n1 ### n1
http://127.0.0.1:6401/n1/statisticsInfo http://127.0.0.1:6401/n1/statisticsInfo